{"id":20253232,"url":"https://github.com/jeremylikness/blazor-wasm","last_synced_at":"2025-04-10T23:33:54.760Z","repository":{"id":37485042,"uuid":"177845639","full_name":"JeremyLikness/blazor-wasm","owner":"JeremyLikness","description":"Blazor and WebAssembly examples (part of a Blazor presentation)","archived":false,"fork":false,"pushed_at":"2020-02-18T21:44:58.000Z","size":324,"stargazers_count":81,"open_issues_count":2,"forks_count":32,"subscribers_count":12,"default_branch":"master","last_synced_at":"2025-04-10T17:33:44.762Z","etag":null,"topics":["blazor","blazor-client","blazor-interop","blazor-web","webassembly"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/JeremyLikness.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-03-26T18:22:17.000Z","updated_at":"2024-11-11T13:45:33.000Z","dependencies_parsed_at":"2022-09-07T09:51:23.915Z","dependency_job_id":null,"html_url":"https://github.com/JeremyLikness/blazor-wasm","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JeremyLikness%2Fblazor-wasm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JeremyLikness%2Fblazor-wasm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JeremyLikness%2Fblazor-wasm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JeremyLikness%2Fblazor-wasm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/JeremyLikness","download_url":"https://codeload.github.com/JeremyLikness/blazor-wasm/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248317347,"owners_count":21083519,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["blazor","blazor-client","blazor-interop","blazor-web","webassembly"],"created_at":"2024-11-14T10:22:30.200Z","updated_at":"2025-04-10T23:33:54.738Z","avatar_url":"https://github.com/JeremyLikness.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Blazor and WebAssembly\n\n[![Build Status](https://jeremylikness.visualstudio.com/blazor-wasm/_apis/build/status/JeremyLikness.blazor-wasm?branchName=master)](https://jeremylikness.visualstudio.com/blazor-wasm/_build/latest?definitionId=9\u0026branchName=master)\n\n![Release Status](https://jeremylikness.vsrm.visualstudio.com/_apis/public/Release/badge/924b10c3-4fcd-47eb-8b03-35e40f04e862/1/1)\n\n[![Free Azure Account](https://img.shields.io/badge/FREE-Azure-0077ff)](https://jlik.me/gmi) Get your [Free Azure Account](https://jlik.me/gmi)\n\nThis repository contains samples for a presentation about using C# and .NET in the browser using WebAssembly with Blazor.\n\n▶ [Get Started with Blazor](https://jlik.me/flj) \n\n👋🏻 [Introduction/Overview of Blazor](https://jlik.me/flk)\n\n🔪 [Intro to Razor Components](https://jlik.me/fll)\n\n\u003e This repository is continuously built and deployed using free Azure Pipelines. If you're interested in how it was setup and configured to build automatically and deploy to low cost Azure Storage Static Websites, read [Deploy WebAssembly from GitHub to Azure Storage Static Websites with Azure Pipelines](https://jlik.me/fzh).\n\n## Presentation\n\n🎦 You can download the related PowerPoint presentation [here](https://jlik.me/fn3).\n\nTo see how Blazor compares to other SPA frameworks like Angular, read: [Angular vs. Blazor](https://blog.jeremylikness.com/blog/2019-01-03_from-angular-to-blazor-the-health-app/).\n\n## Demos\n\nThis section contains step-by-step instructions to execute each of the demos.\n\n### Pre-requisites\n\nThe following should be installed for the demos to work:\n\n* [emscripten](https://emscripten.org/docs/getting_started/downloads.html) for the `asm.js` and WebAssembly demos\n* [http-service (node.js)](https://www.npmjs.com/package/http-server) to serve the \"primes\" example site (any simple web server will do)\n* [Blazor](https://jlik.me/fhs) has full instructions for installing and using Blazor.\n\nThe current version used in this repo is `3.2.0-preview1.20073.1`.\n\n### Build asm.js\n\nNavigate into the primes directory. First, show the speed of the JavaScript version.\n\n`cat primes-js.js`\n\n`node primes-js.js`\n\nNext, show the C code.\n\n`cat primes.c`\n\nThen, compile the C code to asm.js:\n\n`emcc primes.asm.c -s WASM=0 -Os -o primes.asm.js`\n\nShow the expanded code for reference, then run the asm.js version:\n\n`cat primes-asm.js`\n\n`node primes-asm.js`\n\n### Build WebAssembly (Wasm)\n\n👀 [Live Demo](https://jlikme.z13.web.core.windows.net/blazor-wasm/primes/primes.html)\n\nShow the C code:\n\n`cat primes.wasm.c`\n\nCompile the C code to WebAssembly:\n\n`emcc primes.wasm.c -s WASM=1 -Os -o primes.wasm.js`\n\nUse a simple server like `http-server` to serve files in the directory. Navigate to `primes.html`:\n\n[http://localhost:8080/primes.html](http://localhost:8080/primes.html)\n\nOpen the console and show the time with JavaScript vs. WebAssembly.\n\n### Get Started\n\nCreate a new Blazor project with .NET Core hosting. Run the application and step through the tabs.\n\n1. Note the counter resets to zero when you return\n2. Show the `Shared` project defines a `WeatherForecast` class that is shared between the client and the server\n3. Demonstrate service registration in `Startup`\n4. Open `Startup` on the client for similar services\n5. Walk through logic for `Counter.razor`\n6. Point out that `FetchData.razor` uses the `HttpClient` but it is injected for the correct configuration\n7. Activate network debugging in the browser. Refresh and show the DLLs being loaded\n\n### Reusable Components\n\n👀 [Live Demo](https://jlikme.z13.web.core.windows.net/blazor-wasm/ReusableComponents)\n\nCreate a new Blazor project (no hosting, client only).\n\n1. Under `Shared` create a Razor view component and name it `LabelSlider.razor`\n2. Paste the following html:\n\n   ```html\n   \u003cinput type=\"range\" min=\"@Min\" max=\"@Max\" @bind=\"@CurrentValue\" /\u003e\n   \u003cspan\u003e@CurrentValue\u003c/span\u003e\n   ```\n\n3. In a `@code` block add:\n\n   ```csharp\n    [Parameter]\n    public int Min { get; set; }\n\n    [Parameter]\n    public int Max { get; set; }\n\n    [Parameter]\n    public int CurrentValue { get; set; }\n    ```\n\n4. Drop into the `Counter` page:\n\n   ```html\n   \u003cLabelSlider Min=\"0\" Max=\"99\" CurrentValue=\"@currentCount\"/\u003e\n   ```\n\n5. Show how the clicks update the slider, but sliding doesn't update the host page. Also note that the value only updates when you stop sliding, and it only updates on the slider and not on the \"current count\" (although clicking the button will update the slider, the converse isn't true.)\n\n6. Inside `LabelSlider` change the binding to `@bind-value=\"@CurrentValue\"` then add an additional `@bind-value:event=\"oninput\"` to refresh as it is sliding\n\n7. Add an event for the current value changing and implement it (this will replace the existing `CurrentValue` property)\n\n   ```csharp\n    private int _currentValue;\n\n    [Parameter]\n    public int CurrentValue\n    {\n        get =\u003e _currentValue;\n        set\n        {\n            if (value != _currentValue)\n            {\n                _currentValue = value;\n                CurrentValueChanged?.Invoke(value);\n            }\n        }\n    }\n\n    [Parameter]\n    public Action\u003cint\u003e CurrentValueChanged { get; set; }\n    ```\n\n8. Update the binding to `@bind-CurrentValue` in `Counter.razor`\n9. Run and show it is picking up the value, but not refreshing. Explain we'll cover manual UI refresh later.\n\n### Libraries and Interop\n\n👀 [Live Demo](https://jlikme.z13.web.core.windows.net/blazor-wasm/LibrariesInterop)\n\nCreate a new client-only project.\n\n1. In NuGet packages, search for and install `Markdown` [here](https://www.nuget.org/packages/Markdown/)\n2. In `Index.razor` add the following HTML (remove the `SurveyPrompt`):\n\n    ```html\n    \u003ctextarea style=\"width: 100%\" rows=\"5\" @bind=\"@SourceText\"\u003e\u003c/textarea\u003e\n    \u003cbutton @onclick=\"@Convert\"\u003eConvert\u003c/button\u003e\n    \u003cp\u003e@TargetText\u003c/p\u003e\n    ```\n\n3. Add a `@using HeyRed.MarkdownSharp` to the top\n4. Add a `@code` block:\n\n    ```csharp\n    string SourceText { get; set; }\n    string TargetText { get; set; }\n    Markdown markdown = new Markdown();\n\n    void Convert()\n    {\n        TargetText = markdown.Transform(SourceText);\n    }\n    ```\n\n5. Run and show the conversion. Explain that the bindings are \"safe\" and don't expand the HTML.\n\n6. Create a file under `wwwroot` called `markupExtensions.js` and populate it with:\n\n    ```javascript\n    window.markupExtensions = {\n        toHtml: (txt, target) =\u003e {\n            const area = document.createElement(\"textarea\");\n            area.innerHTML = txt;\n            target.innerHTML = area.value;\n        }\n    }\n    ```\n\n7. Reference it from `index.html` under `wwwroot` with `\u003cscript src=\"./markupExtensions.js\"\u003e\u003c/script\u003e`\n8. In `index.razor` remove the `TargetText` references and inject the JavaScript interop: `@inject IJSRuntime JsRuntime`\n9. Change the paragraph element to a reference: `\u003cp @ref=\"Target\"/\u003e`\n10. Update the `@code` to call the JavaScript via interop\n\n    ```csharp\n    string SourceText { get; set; }\n    ElementReference Target;\n    Markdown markdown = new Markdown();\n\n    void Convert()\n    {\n        var html = markdown.Transform(SourceText);\n        JsRuntime.InvokeAsync\u003cobject\u003e(\"markupExtensions.toHtml\", html, Target);\n    }\n    ```\n\n11. Run and show the goodness. Explain `Convert` could be `async` and await a response if necessary\n\n12. Add a class named `MarkdownHost` under `Shared`:\n\n    ```csharp\n    using HeyRed.MarkdownSharp;\n    using Microsoft.JSInterop;\n\n    namespace LibrariesInterop.Shared\n    {\n        public static class MarkdownHost\n        {\n            [JSInvokable]\n            public static string Convert(string src)\n            {\n                return new Markdown().Transform(src);\n            }\n        }\n    }\n    ```\n\n13. Re-run the app and from the console type. Be sure to change `LibrariesInterop` to the name of your project:\n\n    ```javascript\n    alert(DotNet.invokeMethod(\"LibrariesInterop\", \"Convert\", \"# one\\n## two \\n* a \\n* b\"))\n    ```\n\n14. Explain this can also use `Task` to make it asynchronous\n\n### Code Behind\n\n👀 [Live Demo](https://jlikme.z13.web.core.windows.net/blazor-wasm/CodeBehind)\n\nCreate a new client-only project.\n\n1. Create a class under `Pages` named `FetchDataBase` (not to be confused with a database)\n\n    ```csharp\n    public class FetchDataBase : ComponentBase\n    {\n        [Inject]\n        public HttpClient Http { get; set; }\n\n        public WeatherForecast[] forecasts;\n\n        protected override async Task OnInitializedAsync()\n        {\n            forecasts = await Http.GetJsonAsync\u003cWeatherForecast[]\u003e\n                (\"sample-data/weather.json\");\n        }\n\n        public class WeatherForecast\n        {\n            public DateTime Date { get; set; }\n\n            public int TemperatureC { get; set; }\n\n            public int TemperatureF { get; set; }\n\n            public string Summary { get; set; }\n        }\n    }\n    ```\n    \n    These are the using statements:\n    \n    ```csharp\n    using Microsoft.AspNetCore.Components;\n    using System;\n    using System.Net.Http;\n    using System.Threading.Tasks;\n    ```\n\n2. Open `FetchData.razor` and remove the `@Inject` line and entire `@code` block\n3. Add `@inherits FetchDataBase` after the `@page` directive\n4. Run it and show it working\n\n### MVVM Pattern\n\n👀 [Live Demo](https://jlikme.z13.web.core.windows.net/blazor-wasm/MvvmPattern)\n\nCreate a new client-only project.\n\n1. Add a class named `MainModel` to the root\n\n    ```csharp\n    public class MainModel : INotifyPropertyChanged\n    {\n        public event PropertyChangedEventHandler PropertyChanged;\n\n        private int _age = 30;\n\n        public int Age\n        {\n            get =\u003e _age;\n            set\n            {\n                if (value != _age)\n                {\n                    _age = value;\n                    PropertyChanged?.Invoke(value, new PropertyChangedEventArgs(nameof(Age)));\n                }\n            }\n        }\n\n        public int MaximumHeartRate\n        {\n            get\n            {\n                return 220 - _age;\n            }\n        }\n\n        public int TargetHeartRate\n        {\n            get\n            {\n                return (int)(0.85*MaximumHeartRate);\n            }\n        }\n    }\n    ```\n\n    Add a using for `System.ComponentModel`\n\n1. Register the class in `Program`:\n\n    `builder.Services.AddSingleton\u003cMainModel\u003e();`\n\n1. Under `Shared` add `Age.razor`\n\n    ```html\n    @inject MainModel Model\n    Age: \u003cspan style=\"cursor: pointer\" @onclick=\"@(()=\u003eDecrement(true))\"\u003e\n    \u003cstrong\u003e\u0026nbsp;\u0026lt;\u0026nbsp;\u003c/strong\u003e\n    \u003c/span\u003e\n    \u003cinput type=\"range\" min=\"13\" max=\"120\" @bind-value=\"Model.Age\"\n       @bind-value:event=\"oninput\" /\u003e\n    \u003cspan style=\"cursor: pointer\" @onclick=\"@(()=\u003eDecrement(false))\"\u003e\n        \u003cstrong\u003e\u0026nbsp;\u0026gt;\u0026nbsp;\u003c/strong\u003e\n    \u003c/span\u003e\n    \u003cspan\u003e@Model.Age\u003c/span\u003e\n    ```\n\n1. Add the code block:\n\n    ```csharp\n    void Decrement(bool decrement)\n    {\n        if (decrement \u0026\u0026 Model.Age \u003e 13)\n        {\n            Model.Age -= 1;\n        }\n        if (!decrement \u0026\u0026 Model.Age \u003c 120)\n        {\n            Model.Age += 1;\n        }\n    }\n    ```\n\n1. Then add `HeartRate.razor` under `Shared`:\n\n    ```html\n    @inject MainModel Model\n    \u003cdiv\u003e\n        \u003cp\u003eYour target heart rate is: @Model.TargetHeartRate\u003c/p\u003e\n        \u003cp\u003eYour maximum heart rate is: @Model.MaximumHeartRate\u003c/p\u003e\n    \u003c/div\u003e\n    ```\n\n1. Add the new controls to `Index.razor` (remove `SurveyPrompt`):\n\n    ```html\n    \u003cAge/\u003e\n    \u003cHeartRate/\u003e\n    ```\n\n1. Run the app and show that the heart rates aren't updating\n1. Add this `@code` code to the bottom of `HeartRate.razor`\n\n    ```csharp\n    protected override void OnInitialized()\n    {\n        base.OnInitialized();\n        Model.PropertyChanged += (o, e) =\u003e StateHasChanged();\n    }\n    ```\n\n1. Re-run the app and show it working\n1. Explain that this can be done at a higher level to automatically propagate across controls\n\nLearn more about: [MVVM support in Blazor](https://blog.jeremylikness.com/blog/2019-01-04_mvvm-support-in-blazor/).\n\n### Debugging\n\n1. Open URL in Chrome for any of the apps\n2. Show `SHIFT+ALT+D` key press\n3. If instructions appear, close all Chrome instances (including in the system tray) and paste the code to run with debugging enabled\n4. Repeat the key press\n5. Show a breakpoint and discuss this is very limited for now\n\n## Summary\n▶ [Get Started with Blazor](https://jlik.me/flj)\n\n👋🏻 [Introduction/Overview of Blazor](https://jlik.me/flk)\n\n🔪 [Intro to Razor Components](https://jlik.me/fll)  \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjeremylikness%2Fblazor-wasm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjeremylikness%2Fblazor-wasm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjeremylikness%2Fblazor-wasm/lists"}