├── .editorconfig ├── .gitignore ├── .gitmodules ├── .markdownlint.json ├── LICENSE ├── archetypes └── default.md ├── config.toml ├── content ├── _index.md ├── architecture │ ├── _index.md │ ├── dependency-injection.md │ ├── interop.md │ ├── rest-api.md │ └── view-logic-separation.md ├── getting-started │ ├── _index.md │ ├── about.md │ ├── getting-blazor.md │ ├── getting-started.md │ ├── static-hosting.md │ └── what-is-blazor.md ├── pages │ ├── _index.md │ ├── data-binding.md │ ├── dynamic-content.md │ ├── layouts.md │ ├── lifecycle-methods.md │ └── router.md └── terms-of-service.md ├── layouts └── partials │ ├── footer.html │ └── logo.html ├── readme.md ├── samples ├── .gitignore ├── BlazorPages │ ├── App.cshtml │ ├── BlazorPages.csproj │ ├── BlazorPages.csproj.user │ ├── BlazorPages.sln │ ├── Pages │ │ ├── ChildComponent.cshtml │ │ ├── DynamicRenderTree.cs │ │ ├── EventBinding.cshtml │ │ ├── Initialization.cshtml │ │ ├── InitializationParent.cshtml │ │ ├── ManualRefresh.cshtml │ │ ├── ManualRefreshChild.cshtml │ │ ├── ManualRefreshParent.cshtml │ │ ├── OneWayDataBinding.cshtml │ │ ├── RenderFragment.cshtml │ │ ├── TwoWayDataBinding.cshtml │ │ ├── _ViewImports.cshtml │ │ └── menu.cshtml │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Shared │ │ └── MainLayout.cshtml │ ├── Startup.cs │ ├── _ViewImports.cshtml │ └── wwwroot │ │ ├── css │ │ └── site.css │ │ └── index.html ├── DependencyInjection │ ├── App.cshtml │ ├── DependencyInjection.csproj │ ├── DependencyInjection.sln │ ├── Pages │ │ ├── CustomerList.cshtml │ │ ├── Lifetime1.cshtml │ │ ├── Lifetime2.cshtml │ │ ├── LifetimeDemo.cs │ │ └── _ViewImports.cshtml │ ├── Program.cs │ ├── Services │ │ └── Repository.cs │ ├── Shared │ │ └── MainLayout.cshtml │ ├── Startup.cs │ ├── _ViewImports.cshtml │ └── wwwroot │ │ ├── css │ │ └── site.css │ │ └── index.html ├── RestApi │ ├── RestApi.Client │ │ ├── App.cshtml │ │ ├── Pages │ │ │ ├── FetchData.cshtml │ │ │ ├── InteropBasics.cshtml │ │ │ ├── KendoAutocomplete.cshtml │ │ │ ├── StringUtil.cs │ │ │ └── _ViewImports.cshtml │ │ ├── Program.cs │ │ ├── RestApi.Client.csproj │ │ ├── Shared │ │ │ └── MainLayout.cshtml │ │ ├── Startup.cs │ │ ├── _ViewImports.cshtml │ │ └── wwwroot │ │ │ ├── css │ │ │ └── site.css │ │ │ └── index.html │ ├── RestApi.Server │ │ ├── Controllers │ │ │ └── CustomerController.cs │ │ ├── Data │ │ │ └── CustomerContext.cs │ │ ├── Program.cs │ │ ├── RestApi.Server.csproj │ │ └── Startup.cs │ ├── RestApi.Shared │ │ ├── Customer.cs │ │ └── RestApi.Shared.csproj │ └── RestApi.sln ├── RouterDemo │ ├── App.cshtml │ ├── Pages │ │ ├── HelloPlanet.cshtml │ │ ├── HelloUniverse.cshtml │ │ ├── HelloWorld.cshtml │ │ ├── MainMenu.cshtml │ │ ├── NavigateInCode.cshtml │ │ ├── OuterSpace │ │ │ └── HelloMoon.cshtml │ │ └── _ViewImports.cshtml │ ├── Program.cs │ ├── RouterDemo.csproj │ ├── RouterDemo.sln │ ├── Shared │ │ └── MainLayout.cshtml │ ├── Startup.cs │ ├── _ViewImports.cshtml │ └── wwwroot │ │ └── index.html ├── StaticHosting │ ├── App.cshtml │ ├── Dockerfile │ ├── Pages │ │ ├── Page1.cshtml │ │ └── Page2.cshtml │ ├── Program.cs │ ├── StaticHosting.csproj │ ├── StaticHosting.sln │ ├── _ViewImports.cshtml │ ├── nginx.conf │ └── wwwroot │ │ └── index.html └── ViewLogicSeparation │ ├── ViewLogicSeparation.sln │ └── ViewLogicSeparation │ ├── App.cshtml │ ├── Logic │ ├── DataAccess.cs │ └── Model.cs │ ├── Pages │ ├── Inheritance.cshtml │ ├── InheritanceBase.cs │ └── _ViewImports.cshtml │ ├── Program.cs │ ├── Shared │ └── MainLayout.cshtml │ ├── ViewLogicSeparation.csproj │ ├── _ViewImports.cshtml │ └── wwwroot │ ├── css │ └── site.css │ └── index.html └── static ├── images ├── architecture │ └── click-flow.png ├── favicon.png ├── getting-started │ ├── blazor-architecture.jpg │ ├── chrome-load-dlls-061.png │ ├── chrome-load-dlls.png │ ├── vs-project-template.png │ ├── vs-razor-compilation.png │ └── webserver-for-chrome-settings.png ├── logo.svg └── pages │ └── demo-lifecycle.png └── slides ├── Blazor-Intro.pdf └── Blazor-Intro.pptx /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | # Unix-style newlines with a newline ending every file 4 | [*] 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | public 3 | themes 4 | .vs 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "themes/hugo-theme-learn"] 2 | path = themes/hugo-theme-learn 3 | url = https://github.com/matcornic/hugo-theme-learn.git 4 | -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "MD002": false, 3 | "MD041": false, 4 | "MD013": false 5 | } 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 software architects gmbh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /archetypes/default.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "{{ replace .Name "-" " " | title }}" 3 | date = {{ .Date }} 4 | +++ 5 | -------------------------------------------------------------------------------- /config.toml: -------------------------------------------------------------------------------- 1 | languageCode = "en-us" 2 | title = "Learn Blazor" 3 | 4 | # Change the default theme to be use when building the site with Hugo 5 | theme = "hugo-theme-learn" 6 | 7 | # For search functionnality 8 | [outputs] 9 | home = [ "HTML", "RSS", "JSON"] 10 | 11 | [params] 12 | editURL = "https://github.com/software-architects/learn-blazor/tree/master/content/" 13 | author = "Rainer Stropek" 14 | description = "Website with learning resources regarding the Blazor platform" 15 | disableLanguageSwitchingButton = true 16 | themeVariant = "blue" 17 | disclaimer = "The *learn-blazor* site had been created at a point in time when there was no Blazor documentation at all available. Since then, the framework has grown and matured a lot. Documentation [is now available on docs.microsoft.com](https://docs.microsoft.com/en-us/aspnet/core/blazor/). Therefore, much of the content of *learn-blazor* isn't necessary anymore and parts of it are outdated. I hope the site was useful to early adopters. Open [Microsoft's documentation of Blazor...](https://docs.microsoft.com/en-us/aspnet/core/blazor/)" 18 | 19 | [[menu.shortcuts]] 20 | name = " Blazor on GitHub" 21 | identifier = "bg" 22 | url = "https://github.com/aspnet/aspnetcore" 23 | weight = 10 24 | 25 | [[menu.shortcuts]] 26 | name = " Blazor Docs" 27 | identifier = "do" 28 | url = "https://blazor.net/docs/" 29 | weight = 35 30 | 31 | [[menu.shortcuts]] 32 | name = " Terms of Service" 33 | identifier = "ts" 34 | url = "/terms-of-service" 35 | weight = 40 36 | -------------------------------------------------------------------------------- /content/_index.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "Introduction" 3 | chapter = true 4 | +++ 5 | 6 | ### Welcome 7 | 8 | # Learn Blazor 9 | 10 | Blazor is *Browser plus Razor*. Learn how you can build awesome *Single Page Apps* with it. 11 | 12 | {{% notice note %}} 13 | {{% siteparam "disclaimer" %}} 14 | {{% /notice %}} 15 | -------------------------------------------------------------------------------- /content/architecture/_index.md: -------------------------------------------------------------------------------- 1 | +++ 2 | chapter = true 3 | title = "Architecture" 4 | alwaysopen = true 5 | weight = 30 6 | +++ 7 | 8 | ### Learn Blazor 9 | 10 | {{% notice note %}} 11 | {{% siteparam "disclaimer" %}} 12 | {{% /notice %}} 13 | 14 | # Architecture 15 | 16 | Learn about architectural topics in Blazor. 17 | -------------------------------------------------------------------------------- /content/architecture/dependency-injection.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "Dependency Injection" 3 | weight = 10 4 | lastModifierDisplayName = "rainer@software-architects.at" 5 | date = 2019-05-03 6 | +++ 7 | 8 | {{% notice note %}} 9 | This content has been removed because the topic is covered in [Microsoft's Blazor documentation](https://docs.microsoft.com/en-us/aspnet/core/blazor/dependency-injection). 10 | {{% /notice %}} 11 | -------------------------------------------------------------------------------- /content/architecture/interop.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "JavaScript Interop" 3 | weight = 30 4 | lastModifierDisplayName = "rainer@timecockpit.com, christian.schwendtner@gmx.net" 5 | date = 2019-05-03 6 | +++ 7 | 8 | {{% notice note %}} 9 | This content has been removed because the topic is covered in [Microsoft's Blazor documentation](https://docs.microsoft.com/en-us/aspnet/core/blazor/javascript-interop). 10 | {{% /notice %}} 11 | -------------------------------------------------------------------------------- /content/architecture/rest-api.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "Consuming REST APIs" 3 | weight = 20 4 | lastModifierDisplayName = "rainer@software-architects.at" 5 | date = 2018-04-29 6 | +++ 7 | 8 | {{% notice note %}} 9 | {{% siteparam "disclaimer" %}} 10 | {{% /notice %}} 11 | 12 | ## Introduction 13 | 14 | Developers who are used to writing C# code in ASP.NET will find it very simple to consume web APIs with Blazor. All the usual classes (e.g. `System.Net.Http.HttpClient`) and language constructs (e.g. `async` and `await`) are available. Therefore, reading data from a server and printing it in the console looks like this in Blazor: 15 | 16 | ```cs 17 | @page "/" 18 | @inject HttpClient Http 19 | 20 | 21 | 22 | @functions { 23 | private async Task PrintWebApiResponse() 24 | { 25 | var response = await Http.GetStringAsync("/api/Customer"); 26 | Console.WriteLine(response); 27 | } 28 | } 29 | ``` 30 | 31 | Note that we are using [dependency injection](../dependency-injection/) to get an instance of the `System.Net.Http.HttpClient` class. Once we have it, it is business as usual. 32 | 33 | ## What Happens in the Background 34 | 35 | The browser's firewall would not allow Blazor to directly do network communication. All network traffic has to go through the browser. Therefore, Blazor's C# implementation has to pass our HTTP request to Blazor's JavaScript-part. How is that possible if we use C# usual `System.Net.Http.HttpClient` class? 36 | 37 | The solution is the abstract base class `System.Net.Http.HttpMessageHandler`. It represents an abstraction layer for sending HTTP requests as an asynchronous operation. `HttpClient` takes an instance of this class in one of its constructor. Blazor contains an implementation of `HttpMessageHandler` that hands over HTTP requests to JavaScript (`Microsoft.AspNetCore.Blazor.Browser.Http.BrowserHttpMessageHandler`, see [source on GitHub](https://github.com/aspnet/Blazor/blob/release/0.1.0/src/Microsoft.AspNetCore.Blazor.Browser/Http/BrowserHttpMessageHandler.cs)). If you get your `HttpClient` using Blazor's dependency injection, you will get an instance that is already wired up with `BrowserHttpMessageHandler`. 38 | 39 | The real HTTP request is sent using the browser's [*fetch* API](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch) (see [source on GitHub](https://github.com/aspnet/Blazor/blob/release/0.1.0/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Services/Http.ts)). 40 | 41 | ## What About JSON 42 | 43 | Blazor comes with [*SimpleJson*](https://github.com/facebook-csharp-sdk/simple-json) embedded (source code is part of the Blazor DLLs, not a referenced NuGet package), a JSON library for portable .NET apps. The static class `Microsoft.AspNetCore.Blazor.HttpClientJsonExtensions` (see [source on GitHub](https://github.com/aspnet/Blazor/blob/release/0.1.0/src/Microsoft.AspNetCore.Blazor/Json/HttpClientJsonExtensions.cs)) contains extensions methods for `HttpClient` that make it easier to consume JSON-based web APIs in Blazor. 44 | 45 | ## Sample 46 | 47 | The following sample demonstrates the use of a RESTful Web API implemented with ASP.NET Core and Entity Framework. You can find the complete sample [on GitHub](https://github.com/software-architects/learn-blazor/tree/master/samples/RestApi). 48 | 49 | ```cs 50 | @page "/" 51 | @using RestApi.Shared 52 | @inject HttpClient Http 53 | 54 |

Customer List

55 | 56 |

This component demonstrates fetching data from the server.

57 | 58 | 59 | 60 | 61 | 62 | @if (Customers == null) 63 | { 64 |

Loading...

65 | } 66 | else 67 | { 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | @foreach (var customer in Customers) 79 | { 80 | 81 | 82 | 83 | 84 | 85 | 86 | } 87 | 88 |
IDFirst NameLast NameDepartment
@customer.ID@customer.FirstName@customer.LastName@customer.Department
89 | } 90 | @functions { 91 | private Customer[] Customers { get; set; } 92 | 93 | protected override async Task OnInitAsync() 94 | { 95 | await RefreshCustomerList(); 96 | } 97 | 98 | private async Task RefreshCustomerList() 99 | { 100 | Customers = await Http.GetJsonAsync("/api/Customer"); 101 | StateHasChanged(); 102 | } 103 | 104 | private async Task FillWithDemoData() 105 | { 106 | for (var i = 0; i < 10; i++) 107 | { 108 | await Http.SendJsonAsync(HttpMethod.Post, "/api/Customer", new Customer 109 | { 110 | FirstName = "Tom", 111 | LastName = $"Customer {i}", 112 | Department = i % 2 == 0 ? "Sales" : "Research" 113 | }); 114 | } 115 | 116 | await RefreshCustomerList(); 117 | } 118 | 119 | private async Task DeleteAllCustomers() 120 | { 121 | foreach (var c in Customers) 122 | { 123 | await Http.DeleteAsync($"/api/Customer/{c.ID}"); 124 | } 125 | 126 | await RefreshCustomerList(); 127 | } 128 | 129 | private async Task PrintWebApiResponse() 130 | { 131 | var response = await Http.GetStringAsync("/api/Customer"); 132 | Console.WriteLine(response); 133 | } 134 | } 135 | ``` 136 | -------------------------------------------------------------------------------- /content/architecture/view-logic-separation.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "View and Logic" 3 | weight = 40 4 | lastModifierDisplayName = "rainer@software-architects.at" 5 | date = 2018-04-29 6 | +++ 7 | 8 | {{% notice note %}} 9 | {{% siteparam "disclaimer" %}} 10 | {{% /notice %}} 11 | 12 | ## Introduction 13 | 14 | The default Blazor templates generate view logic code inside the Razor template using `@functions`. In the background, Blazor generates a single class containing C# code for generating the tree of view objects as well as the C# code representing the view logic. 15 | 16 | ```html 17 | @page "/" 18 | 19 | 20 | 21 | @functions { 22 | // Logic (C#) 23 | } 24 | ``` 25 | 26 | Many developers dislike mixing view and logic in a single file. In this article, we explore ways to separate view and logic. 27 | 28 | ## Dependency Injection 29 | 30 | You should consider using application services and Blazor's dependency injection system to isolate logic that is independent of the view (e.g. core business logic, data access logic etc.). [Read more about dependency injection in Blazor...](../dependency-injection/). 31 | 32 | ## Partial Classes 33 | 34 | At the time of writing, Blazor does *not* generate `partial` classes for its components ([related issue on GitHub](https://github.com/aspnet/Blazor/issues/278)). Therefore, you cannot just create a separate file and use the same class name as the component. 35 | 36 | ## Base Class 37 | 38 | {{% notice note %}} 39 | You can find the complete source code of the sample below [on GitHub](https://github.com/software-architects/learn-blazor/tree/master/samples/ViewLogicSeparation). 40 | {{% /notice %}} 41 | 42 | One way of separating view and logic is to create a base class. The base class contains all the view logic (C#). The Blazor component derives from this class and adds the view. Let us look at an example. Here is the view. Note that it does not have any code. It just uses properties and methods defined in its base class `InheritanceBase` (note the `@inherits` statement at the beginning of the file). 43 | 44 | ```html 45 | @page "/" 46 | @inherits InheritanceBase 47 | 48 |

Inheritance

49 | 50 |
51 | 52 |
54 | 55 | @if (CanGetCustomers) 56 | { 57 | 58 | } 59 |
60 | 61 | 62 | @if (CustomersLoaded) 63 | { 64 |
65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | @foreach (var customer in Customers) 74 | { 75 | 76 | 77 | 78 | 79 | } 80 | 81 |
First NameLast Name
@customer.FirstName@customer.FirstName
82 |
83 | } 84 | 85 | 86 | ``` 87 | 88 | Now let us look the base class containing the view logic. Note that this class does also not contain any business or data access logic. It consists of *view logic* (i.e. logic that defines the *behavior* of the view, not its design). I added comments to make it easier for you to recognize the important concepts used in this example. 89 | 90 | ```cs 91 | using Microsoft.AspNetCore.Blazor.Components; 92 | using System; 93 | using System.Collections.Generic; 94 | using System.Net.Http; 95 | using ViewLogicSeparation.Logic; 96 | 97 | namespace ViewLogicSeparation.Pages 98 | { 99 | public class InheritanceBase : BlazorComponent 100 | { 101 | #region Injected properties 102 | // Note that Blazor's dependency injection works even if you use the 103 | // `InjectAttribute` in a component's base class. 104 | 105 | // Note that we decouple the component's base class from 106 | // the data access service using an interface. 107 | [Inject] 108 | protected IDataAccess DataAccess { get; set; } 109 | #endregion 110 | 111 | #region Properties used for data binding 112 | public IEnumerable Customers { get; set; } 113 | 114 | public string CustomerFilter { get; set; } 115 | #endregion 116 | 117 | #region Status properties used to enable/disable/hide/show UI elements 118 | public bool CustomersLoaded => Customers != null; 119 | 120 | public bool IsInitialized => DataAccess != null; 121 | 122 | public bool CanGetCustomers => IsInitialized && !string.IsNullOrEmpty(CustomerFilter); 123 | #endregion 124 | 125 | // At the time of writing, `@onclickasync` does not exist. Therefore, 126 | // the return type has to be `void` instead of `Task`. 127 | public async void GetCustomers() 128 | { 129 | #region Check prerequisites 130 | if (DataAccess == null) 131 | { 132 | // This should never happen. This check should demonstrate that 133 | // `DataAccess` and `Http` are filled by Blazor's dependency injection. 134 | throw new InvalidOperationException("There is something wrong with DI"); 135 | } 136 | 137 | if (!CanGetCustomers) 138 | { 139 | // This should never happen. The UI should prevent calling 140 | // `GetCustomerAsync` if no customer filter has been set (e.g. by 141 | // disabling or hiding the button). 142 | throw new InvalidOperationException("Customer filter not set"); 143 | } 144 | #endregion 145 | 146 | // Get the data using the injected data access service 147 | Customers = await DataAccess.GetCustomersAsync(CustomerFilter); 148 | 149 | // We have to manually call `StateHasChanged` because Blazor's `onclick` 150 | // does not yet support async handler methods. 151 | StateHasChanged(); 152 | } 153 | } 154 | } 155 | ``` 156 | 157 | To be continued... 158 | -------------------------------------------------------------------------------- /content/getting-started/_index.md: -------------------------------------------------------------------------------- 1 | +++ 2 | chapter = true 3 | title = "Getting Started" 4 | alwaysopen = true 5 | weight = 10 6 | +++ 7 | 8 | ### Learn Blazor 9 | 10 | # Getting Started 11 | 12 | {{% notice note %}} 13 | {{% siteparam "disclaimer" %}} 14 | {{% /notice %}} 15 | 16 | Learn how you can get started with Blazor. 17 | 18 | -------------------------------------------------------------------------------- /content/getting-started/about.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "About" 3 | weight = 5 4 | lastModifierDisplayName = "rainer@software-architects.at" 5 | date = 2018-10-01 6 | +++ 7 | 8 | 9 | 10 | ## Introduction 11 | 12 | I am a passionate web developer. The technology stack I mostly use is ASP.NET Core (C#) on the server and Angular (TypeScript) on the client. Having to constantly switch between two programming languages and frameworks isn't very efficient. Because of this, I started using Node.js on the server some years ago. It allows me to use TypeScript consistently. 13 | 14 | Blazor is tempting for me because it also allows me to focus on one programming language. However, this time I can use my favorite language C#. Awesome :-) 15 | 16 | At the time of writing, Blazor is available as a preview release. Its first public preview has been [announced](https://blogs.msdn.microsoft.com/webdev/2018/03/22/get-started-building-net-web-apps-in-the-browser-with-blazor/) on March 22nd, 2018. 17 | 18 | Microsoft [has announced](https://channel9.msdn.com/Events/dotnetConf/2018/S207) that they plan to release the [Server-side Hosting Model](https://docs.microsoft.com/en-us/aspnet/core/blazor/hosting-models?view=aspnetcore-3.0#server-side) with [.NET Core 3](https://blogs.msdn.microsoft.com/dotnet/2018/05/07/net-core-3-and-support-for-windows-desktop-applications/). 19 | 20 | ## Target Audience 21 | 22 | This website is for developers who want to experiment with Blazor at this early stage of the project. 23 | 24 | {{% notice note %}} 25 | {{% siteparam "disclaimer" %}} 26 | {{% /notice %}} 27 | 28 | ## Samples 29 | 30 | If you want to execute and experiment with the code shown on this page, download the complete samples [from GitHub](https://github.com/software-architects/learn-blazor/tree/master/samples). 31 | 32 | ## Slides 33 | 34 | Feel free to look at or use my slide deck for Blazor introductions: 35 | 36 | * [PPTX](../../slides/Blazor-Intro.pptx) 37 | * [PDF](../../slides/Blazor-Intro.pdf) 38 | 39 | ## Questions? 40 | 41 | Do you have questions? Found a bug in this website? 42 | 43 | * Contact me on at [@rstropek](https://twitter.com/rstropek) 44 | * Use the discussion area at the bottom of each page. 45 | * [Create an issue](https://github.com/software-architects/learn-blazor/issues) on 46 | * [Contribute a pull request](https://github.com/software-architects/learn-blazor) on 47 | -------------------------------------------------------------------------------- /content/getting-started/getting-blazor.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "Getting Blazor" 3 | weight = 20 4 | lastModifierDisplayName = "rainer@software-architects.at" 5 | date = 2018-10-01 6 | +++ 7 | 8 | {{% notice note %}} 9 | This content has been removed because the topic is covered in [Microsoft's Blazor documentation](https://docs.microsoft.com/en-us/aspnet/core/blazor/get-started). 10 | {{% /notice %}} 11 | -------------------------------------------------------------------------------- /content/getting-started/getting-started.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "Getting Started" 3 | weight = 30 4 | lastModifierDisplayName = "rainer@software-architects.at" 5 | date = 2018-03-23 6 | +++ 7 | 8 | {{% notice note %}} 9 | This content has been removed because the topic is covered in [Microsoft's Blazor documentation](https://docs.microsoft.com/en-us/aspnet/core/blazor/get-started). 10 | {{% /notice %}} 11 | -------------------------------------------------------------------------------- /content/getting-started/static-hosting.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "Hosting Blazor" 3 | weight = 40 4 | lastModifierDisplayName = "rainer@software-architects.at" 5 | date = 2018-03-22 6 | +++ 7 | 8 | {{% notice note %}} 9 | {{% siteparam "disclaimer" %}} 10 | {{% /notice %}} 11 | 12 | ## Introduction 13 | 14 | Hosting a Blazor app should be trivial, shouldn' it? It is just a bunch of static files (e.g. HTML, JavaScript, DLLs, etc.) so we can put it on any static web server and we are done. This idea is not wrong. If you try it, basic tests will succeed. However, you will recognize problems with routing once you look closer. 15 | 16 | ## The Problem 17 | 18 | Let's take a look at an example. Imagine a Blazor app consisting of two pages, *Page1.cshtml* and *Page2.cshtml*. Let's assume that it is hosted on a simple static web server (e.g. [nginx](https://nginx.org/en/)). Personally, I like to use [*Docker* containers](https://hub.docker.com/_/nginx/) for that. A simple demo *Dockerfile* for statically hosting Blazor in *nginx* could look like this: 19 | 20 | ```Dockerfile 21 | FROM nginx:alpine 22 | COPY ./bin/Debug/netstandard2.0/dist /usr/share/nginx/html/ 23 | ``` 24 | 25 | Let's assume that our *nginx* server listens on *http://localhost:8082/*. If you open this URL, you will see your default route and all the router links will work as expected. However, if you enter *http://localhost:8082/Page1* manually in your browser's address bar, you will get a 404 *not found* error. 26 | 27 | The reason is the difference between client- and server-side routing. If you load your Blazor app without a route, the webserver will send your *index.html* page to the browser client. It contains Blazor's JavaScript and everything is fine. The JavaScript code handles routing on the client-side. If you try to navigate directly to a route in your Blazor app, the URL (e.g. *http://localhost:8082/Page1*) is sent to the *server* and it does not know what *Page1* means. Therefore, you see a 404 error. 28 | 29 | ## The Solution 30 | 31 | We have to configure our static web server to always deliver *index.html* if it receives a URL that will be handled by Blazor's router on the client. Once *index.html* is on the client, it's referenced JavaScript/C# code will care for proper processing of the route. 32 | 33 | The *nginx* server mentioned above allows to define such rules in its config files. Here is a simplified example for an *nginx.conf* file that sends *index.html* whenever it cannot find a corresponding file on disk. 34 | 35 | {{% notice note %}} 36 | Note that this config file is very much simplified to demonstrate the concept. For a production web server, your config file would probably look quite different ([read more about nginx config files](https://docs.nginx.com/nginx/admin-guide/basic-functionality/managing-configuration-files/)). 37 | {{% /notice %}} 38 | 39 | ```config 40 | events { } 41 | http { 42 | server { 43 | listen 80; 44 | 45 | location / { 46 | root /usr/share/nginx/html; 47 | try_files $uri $uri/ /index.html =404; 48 | } 49 | } 50 | } 51 | ``` 52 | 53 | If you want to try *nginx* in Docker, add one line to your Dockerfile and try your Blazor app. Routes like *http://localhost:8082/Page1* will now work. 54 | 55 | ```Dockerfile 56 | FROM nginx:alpine 57 | COPY ./bin/Debug/netstandard2.0/dist /usr/share/nginx/html/ 58 | COPY nginx.conf /etc/nginx/nginx.conf 59 | ``` 60 | 61 | If you use different webservers, the configuration settings will be different but the general concept is the same. The [*Webserver for Chrome*](https://chrome.google.com/webstore/detail/web-server-for-chrome/ofhbbkphhbklhfoeikjpcbhemlocgigb/related?utm_source=chrome-app-launcher-info-dialog) for instance offers rewrite options for Single Page Apps in its advanced options: 62 | 63 | ![Settings Webserver for Chrome](/images/getting-started/webserver-for-chrome-settings.png) 64 | 65 | ## GitHub Pages 66 | 67 | [GitHub Pages](https://pages.github.com/) can also be used to host Single Page Apps like Blazor. You can find a description of the necessary steps to make routing work e.g. [on this website](http://spa-github-pages.rafrex.com/). 68 | -------------------------------------------------------------------------------- /content/getting-started/what-is-blazor.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "What is Blazor?" 3 | weight = 10 4 | lastModifierDisplayName = "rainer@software-architects.at" 5 | date = 2018-10-01 6 | +++ 7 | 8 | {{% notice note %}} 9 | {{% siteparam "disclaimer" %}} 10 | {{% /notice %}} 11 | 12 | ## WebAssembly Changes the Game 13 | 14 | In the past, JavaScript had a monopoly in client-side web development. As developers, we had the choice of frameworks (e.g. Angular, React, etc.) but at the end it always boiled down to JavaScript. [WebAssembly](http://webassembly.org/) changes that. 15 | 16 | > It is a low-level assembly-like language with a compact binary format that provides a way to run code written in multiple languages on the web at near native speed. 17 | 18 | Covering WebAssembly in details is out-of-scope of this website. If you want to learn more, here are some important links to additional material: 19 | 20 | * [webassembly.org](http://webassembly.org/) 21 | * [WebAssembly on MDN](https://developer.mozilla.org/en-US/docs/WebAssembly) 22 | * [WebAssembly on GitHub](https://github.com/webassembly) 23 | * [WebAssembly Web API](https://www.w3.org/TR/wasm-web-api-1/) 24 | 25 | 26 | ## WebAssembly and C\# 27 | 28 | 29 | JavaScript is a powerful language but it has its disadvantages. Some are fixed by [TypeScript](https://www.typescriptlang.org/). However, using C# for client-side web development is compelling for many people because of reasons like the following: 30 | 31 | * C# is a very robust and feature-rich language that has proven to be successful for projects and teams of all sizes 32 | * Existing C# code could be re-used 33 | * [ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/) is a powerful programming framework for server-side web development. Enabling C# on the client would allow teams to use a common technology stack on server and client. 34 | 35 | [Mono](http://www.mono-project.com/) is an open source implementation of Microsoft's .NET Framework based on the ECMA standards for C# and the *Common Language Runtime* (CLR). In 2017, the Mono-team has [published first results](http://www.mono-project.com/news/2017/08/09/hello-webassembly/) of their attempts to bring Mono - and with it C#, the CLR, and the .NET Framework - to WebAssembly. 36 | 37 | > At the time of writing, Mono's C runtime is compiled into WebAssembly, and then Mono’s IL interpreter is used to run managed code. 38 | 39 | A prototype for statically compiling managed code into one [*.wasm* file](https://developer.mozilla.org/en-US/docs/WebAssembly/Text_format_to_wasm) [already exists](http://www.mono-project.com/news/2018/01/16/mono-static-webassembly-compilation/). It is possible, if not likely, that Blazor will move away from interpreting IL towards the statically compiled model over time. 40 | 41 | The following images illustrates the overall architecture of Blazor. 42 | 43 | ![Blazor Architecture](/images/getting-started/blazor-architecture.jpg) 44 | 45 | The following images illustrates the boot process of a Blazor app in Chrome. The app (*counter*) includes Blazor's JavaScript (*blazor.js*). It uses Mono's JavaScript library (*mono.js*) to bootstrap the Mono runtime (*mono.wasm*) in WebAssembly. It then loads the app's DLL (*WebApplication2.dll*) and the DLLs of the .NET Framework. 46 | 47 | ![Loading Blazor app in Chrome](/images/getting-started/chrome-load-dlls-061.png) 48 | 49 | Blazor uses [Mono's *IL Linker*](https://github.com/mono/linker) to reduce the size of your app. You see the *IL Linker* in action by looking at the debug output: 50 | 51 | ```txt 52 | ... 53 | _LinkBlazorApplication: 54 | dotnet "C:\Users\r.stropek\.nuget\packages\microsoft.aspnetcore.blazor.build\0.6.0-preview1-final\targets\../tools/illink/illink.dll" -l none --verbose --strip-security true --exclude-feature com --exclude-feature sre -v false -c link -u link -b true -d "C:\Users\r.stropek\.nuget\packages\microsoft.aspnetcore.blazor.build\0.6.0-preview1-final\targets\../tools/mono/bcl/" -d "C:\Users\r.stropek\.nuget\packages\microsoft.aspnetcore.blazor.build\0.6.0-preview1-final\targets\../tools/mono/bcl/Facades/" -o "C:\Code\GitHub\learn-blazor\samples\BlazorPages\obj\Debug\netstandard2.0\blazor\linker/" -x "C:\Users\r.stropek\.nuget\packages\microsoft.aspnetcore.blazor.build\0.6.0-preview1-final\targets\BuiltInBclLinkerDescriptor.xml" -x "C:\Code\GitHub\learn-blazor\samples\BlazorPages\obj\Debug\netstandard2.0\blazor\linker.descriptor.xml" -a "C:\Users\r.stropek\.nuget\packages\microsoft.aspnetcore.blazor\0.6.0-preview1-final\lib\netstandard2.0\Microsoft.AspNetCore.Blazor.dll" -a "C:\Users\r.stropek\.nuget\packages\microsoft.aspnetcore.blazor.browser\0.6.0-preview1-final\lib\netstandard2.0\Microsoft.AspNetCore.Blazor.Browser.dll" -a "C:\Users\r.stropek\.nuget\packages\microsoft.aspnetcore.blazor.build\0.6.0-preview1-final\lib\netstandard1.0\Microsoft.AspNetCore.Blazor.TagHelperWorkaround.dll" -a "C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.extensions.dependencyinjection\2.1.0\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.dll" -a "C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.extensions.dependencyinjection.abstractions\2.1.0\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll" -a "C:\Users\r.stropek\.nuget\packages\microsoft.jsinterop\0.6.0-preview1-final\lib\netstandard2.0\Microsoft.JSInterop.dll" -a "C:\Users\r.stropek\.nuget\packages\mono.webassembly.interop\0.6.0-preview1-final\lib\netstandard2.0\Mono.WebAssembly.Interop.dll" -a "C:\Code\GitHub\learn-blazor\samples\BlazorPages\obj\Debug\netstandard2.0\BlazorPages.dll" 55 | Processing embedded resource linker descriptor: mscorlib.xml 56 | ... 57 | ``` 58 | 59 | It is possible to configure the linker on a per-assembly basis ([read more](https://blazor.net/docs/host-and-deploy/configure-linker.html)). 60 | 61 | As you can see, Blazor is **not** just a new [Silverlight](https://en.wikipedia.org/wiki/Microsoft_Silverlight). The biggest difference is that it does not require a plugin. You will learn about other differences later. 62 | 63 | ## Razor 64 | 65 | > [Razor](https://github.com/aspnet/Razor) is a template engine that combines C# with HTML to create dynamic web content. 66 | 67 | Razor has its roots on the server where it is typically used to dynamically generate HTML. In Blazor, Razor is used on the client. To be more specific, the Razor engine runs during compilation to generate C# Code from Razor templates. 68 | 69 | The following image illustrates the process. On the right side, you see the Razor template. On the left side, you see the C# code that is generated from this template. 70 | 71 | ![Razor template compilation](/images/getting-started/vs-razor-compilation.png) 72 | 73 | ## HTML Output 74 | 75 | ### Overview 76 | 77 | Unlike former platforms like Silverlight, it does **not** bring its own rendering engine to paint pixels on the screen. 78 | 79 | > Blazor uses the Browser's DOM to display data. 80 | 81 | However, the C# code running in WebAssembly cannot access the DOM directly. It has to go through JavaScript. At the time of writing, the process works like this: 82 | 83 | {{}} 84 | sequenceDiagram 85 | participant CSharp 86 | participant JavaScript 87 | participant DOM 88 | CSharp->>JavaScript: Render Tree 89 | JavaScript->>DOM: Change DOM 90 | Note left of DOM: User triggers event (e.g. click) 91 | JavaScript->>CSharp: Event 92 | Note left of CSharp: Process event 93 | CSharp->>JavaScript: UI differences 94 | JavaScript->>DOM: Change DOM 95 | {{< /mermaid >}} 96 | 97 | 1. The C#-part of Blazor creates a [*Render Tree*](https://github.com/aspnet/Blazor/tree/master/src/Microsoft.AspNetCore.Blazor/RenderTree) which is a tree of UI items. 98 | 99 | 1. The render tree is passed from WebAssembly to the [*Rendering*](https://github.com/aspnet/Blazor/tree/master/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Rendering) in the JavaScript-part of Blazor. It executes the corresponding DOM changes. 100 | 101 | 1. Whenever the user interacts with the DOM (e.g. mouse click, enter text, etc.), the JavaScript-part of Blazor [dispatches an event to C#](https://github.com/aspnet/Blazor/blob/master/src/Microsoft.AspNetCore.Blazor.Browser/Rendering/BrowserRendererEventDispatcher.cs). 102 | 103 | 1. The event is processed by the C#-code of the web app. 104 | 105 | 1. If the DOM changes, a [*Render Batch*](https://github.com/aspnet/Blazor/blob/release/0.1.0/src/Microsoft.AspNetCore.Blazor/Rendering/RenderBatch.cs) with all the UI tree **differences** (**not** the entire UI tree) is built in C# and given to a JavaScript Blazor method that applies the DOM changes. 106 | 107 | Because Blazor is using the regular browser DOM, all usual DOM mechanisms including CSS work keep working. 108 | 109 | ### Renderer 110 | 111 | In Blazor, *renderers* (classes derived from the abstract class `Microsoft.AspNetCore.Blazor.Rendering.Renderer`, see [source on GitHub](https://github.com/aspnet/Blazor/blob/master/src/Microsoft.AspNetCore.Blazor/Rendering/Renderer.cs)) provide mechanisms for rendering hierarchies of *components* (classes implementing `Microsoft.AspNetCore.Blazor.Components.IComponent`, see [source on GitHub](https://github.com/aspnet/Blazor/blob/master/src/Microsoft.AspNetCore.Blazor/Components/IComponent.cs)), dispatching events to them, and notifying when the user interface is being updated. 112 | 113 | For running in the browser, Blazor comes with a *browser renderer* (`Microsoft.AspNetCore.Blazor.Browser.Rendering.BrowserRenderer`, see [source on GitHub](https://github.com/aspnet/Blazor/blob/master/src/Microsoft.AspNetCore.Blazor.Browser/Rendering/BrowserRenderer.cs)). For server-side hosting, there is a *remote renderer* (`Microsoft.AspNetCore.Blazor.Browser.Rendering.RemoteRenderer`, see [source on GitHub](https://github.com/aspnet/Blazor/blob/master/src/Microsoft.AspNetCore.Blazor.Server/Circuits/RemoteRenderer.cs)). For unit tests, Blazor currently uses a *test renderer* (`Microsoft.AspNetCore.Blazor.Test.Helpers`, see [source on GitHub](https://github.com/aspnet/Blazor/blob/master/test/shared/TestRenderer.cs)) 114 | -------------------------------------------------------------------------------- /content/pages/_index.md: -------------------------------------------------------------------------------- 1 | +++ 2 | chapter = true 3 | title = "Creating Pages" 4 | alwaysopen = true 5 | weight = 20 6 | +++ 7 | 8 | ### Learn Blazor 9 | 10 | {{% notice note %}} 11 | {{% siteparam "disclaimer" %}} 12 | {{% /notice %}} 13 | 14 | # Creating Blazor Pages 15 | 16 | Learn how to create Blazor pages. 17 | -------------------------------------------------------------------------------- /content/pages/data-binding.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "Data Binding" 3 | weight = 20 4 | lastModifierDisplayName = "rainer@software-architects.at" 5 | date = 2018-10-02 6 | +++ 7 | 8 | {{% notice note %}} 9 | {{% siteparam "disclaimer" %}} 10 | {{% /notice %}} 11 | 12 | ## One-Way Data Binding 13 | 14 | If you know [Razor](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/razor) from ASP.NET, Razor in Blazor will be quite straight forward for you. The following example shows how you can do one-way data binding in Blazor templates. Note that it is not necessary to trigger UI refresh manually because the change is triggered by a button click. Blazor recognizes changes in that case automatically. 15 | 16 | I have been doing quite a lot of Angular work. Therefore, I added some comments about how the data binding scenarios relate to Angular constructs that you maybe know. 17 | 18 | ```cs 19 | @page "/one-way-data-binding" 20 | 21 | 22 | 23 | 24 | 25 | 29 |

Counter: @Count

30 | 31 | 35 | @if (ShowWarning) 36 | { 37 |

Warning!

38 | } 39 | 40 | 45 | 46 | 47 | 51 |

Notification

52 | 53 | 58 |

This is a note

59 |

This is a note

60 | 61 | 65 |
    66 | @foreach (var number in Numbers) 67 | { 68 |
  • @number
  • 69 | } 70 |
71 | 72 | @functions { 73 | private int Count { get; set; } = 0; 74 | private bool ShowWarning { get; set; } = true; 75 | private string Background { get; set; } = "red"; 76 | private bool NoteIsActive { get; set; } = true; 77 | private List Numbers { get; set; } = new List { 1, 2, 3 }; 78 | private string HighlightClass { get; set; } = "highlight"; 79 | 80 | private void ChangeValues() 81 | { 82 | Count++; 83 | ShowWarning = !ShowWarning; 84 | HighlightClass = HighlightClass == null ? "highlight" : null; 85 | Background = Background == "red" ? "green" : "red"; 86 | NoteIsActive = !NoteIsActive; 87 | Numbers.Add(Numbers.Max() + 1); 88 | } 89 | } 90 | ``` 91 | 92 | ## Two-Way Data Binding 93 | 94 | Blazor already supports two-way data binding using `bind=...`. The following example demonstrates some two-way data-binding scenarios.The comments in the code describe details about the used binding mechanisms: 95 | 96 | ```cs 97 | @page "/two-way-data-binding" 98 | 99 |

100 | @* You can bind using @Property or @Field *@ 101 | Enter your name:
102 | 103 | @* Alternatively also using "Property" or "Field" *@ 104 | What is your age?
105 | 106 | @* Note how to pass a format for DateTime *@ 107 | What is your birthday (culture-invariant default format)?
108 | What is your birthday (German date format)?
109 | 110 | @* Data binding for checkboxes with boolean properties *@ 111 | Are you an administrator?
112 | 113 | @* Data binding for selects with enums *@ 114 | 119 | 120 | @* 121 | The following line would not work because decimal is not supported 122 | What is your salery?
123 | *@ 124 |

125 | 126 |

Hello, @Name!

127 | 128 |

You are @Age years old. You are born on the @Birthday. You are @TypeOfEmployee.

129 | 130 | @if (IsAdmin) 131 | { 132 |

You are an administrator!

133 | } 134 | 135 | @functions { 136 | private enum EmployeeType { Employee, Contractor, Intern }; 137 | private EmployeeType TypeOfEmployee { get; set; } = EmployeeType.Employee; 138 | 139 | private string Name { get; set; } = "Tom"; 140 | private bool IsAdmin { get; set; } = true; 141 | private int Age { get; set; } = 33; 142 | public DateTime Birthday { get; set; } = DateTime.Today; 143 | 144 | public decimal Salary { get; set; } 145 | } 146 | ``` 147 | 148 | ## Bind Value to Child Component 149 | 150 | You can use data binding for communication between components. Here is an example that demonstrates how to bind a value in a parent component to a child component. The child component uses to value to generate a list of value (in practice this could be e.g. due to a web api response). 151 | 152 | *Parent:* 153 | 154 | ```cs 155 | ... 156 | 157 | ... 158 | @functions { 159 | private int CurrentValue { get; set; } 160 | ... 161 | } 162 | ``` 163 | 164 | *Child:* 165 | 166 | ```cs 167 | @using System.Collections.Generic 168 | ... 169 |

You requested @NumberOfElements elements. Here they are:

170 |
    171 | @foreach (var n in Numbers) 172 | { 173 |
  • @n
  • 174 | } 175 |
176 | ... 177 | @functions { 178 | ... 179 | [Parameter] 180 | public int NumberOfElements { get; set; } 181 | 182 | private IEnumerable Numbers 183 | { 184 | get 185 | { 186 | for (var i = 0; i < NumberOfElements; i++) 187 | { 188 | yield return i; 189 | } 190 | } 191 | } 192 | ... 193 | } 194 | ``` 195 | 196 | ## Manually Trigger UI Refresh 197 | 198 | Blazor detects a necessary UI refresh automatically in many scenarios (e.g. after button click). However, there are situations in which you want to trigger a UI refresh manually. Use the `BlazorComponent.StateHasChanged` method for that as shown in the following sample. It changes the application's state using a timer. 199 | 200 | ```cs 201 | @page "/manual-refresh" 202 | @using System.Threading; 203 | 204 |

@Count

205 | 206 | 207 | 208 | @functions { 209 | private int Count { get; set; } = 10; 210 | 211 | void StartCountdown() 212 | { 213 | var timer = new Timer(new TimerCallback(_ => 214 | { 215 | if (Count > 0) 216 | { 217 | Count--; 218 | 219 | // Note that the following line is necessary because otherwise 220 | // Blazor would not recognize the state change and not refresh the UI 221 | this.StateHasChanged(); 222 | } 223 | }), null, 1000, 1000); 224 | } 225 | } 226 | ``` 227 | 228 | Note that `StateHasChanged` only triggers a UI refresh for the current component. It does not automatically refresh its child or parent components. 229 | 230 | ## Event Binding 231 | 232 | At the time or writing, event binding is quite limited in Blazor. Just `onclick` and `onchange` are supported. However, this is currently changing. You find more details in the Blazor GitHub issue [#503](https://github.com/aspnet/Blazor/issues/503). 233 | 234 | ```cs 235 | @page "/event-binding" 236 | 237 | 238 | 239 | 240 | 241 | 242 | Console.WriteLine(newValue)) /> 243 | 244 | 245 | 246 | @functions { 247 | private void Clicked() 248 | { 249 | Console.WriteLine("Hello World"); 250 | } 251 | 252 | private void ChildEventClicked() 253 | { 254 | Console.WriteLine("Child event clicked"); 255 | } 256 | } 257 | ``` 258 | 259 | Components can offer callbacks that parent components can use to react on events raised by their child components. Imagine the following child component: 260 | 261 | ```cs 262 | 263 | 264 | @functions { 265 | public Action OnSomeEvent { get; set; } 266 | 267 | private void OnClick() 268 | { 269 | OnSomeEvent?.Invoke(); 270 | } 271 | } 272 | ``` 273 | 274 | The parent component can handle on `OnSomeEvent` like this (Note that the typecast in the binding is temporary, it will not be necessary in future release of Blazor): 275 | 276 | ```cs 277 | ... 278 | 279 | 280 | @functions { 281 | ... 282 | private void ChildEventClicked() 283 | { 284 | Console.WriteLine("Child event clicked"); 285 | } 286 | } 287 | ``` 288 | -------------------------------------------------------------------------------- /content/pages/dynamic-content.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "Dynamic Content" 3 | weight = 30 4 | lastModifierDisplayName = "rainer@software-architects.at" 5 | date = 2018-04-29 6 | +++ 7 | 8 | {{% notice note %}} 9 | {{% siteparam "disclaimer" %}} 10 | {{% /notice %}} 11 | 12 | ## Dynamic Component 13 | 14 | Sometimes you want create HTML using an algorithm instead of a template. Think of a chess board. It would be boring to create the HTML table by hand. In Blazor, you can ignore the template and create the component fully in C#. The following code sample demonstrates that. It creates a tic-tac-toe board with nine cells and some CSS classes for formatting. 15 | 16 | The class that is used for dynamically generating content is `Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeBuilder` ([source on GitHub](https://github.com/aspnet/Blazor/blob/release/0.1.0/src/Microsoft.AspNetCore.Blazor/RenderTree/RenderTreeBuilder.cs)). It contains methods to open elements, add attributes, add content, add components, etc. Take a look at the source code or use IntelliSense in Visual Studio to see all available render methods. 17 | 18 | Note that you can put this class anywhere in your project. Just make sure to add the `Route` attribute as shown in the example below. [Blazor's router](../router/) will work just fine for your code-only component. 19 | 20 | ```cs 21 | using Microsoft.AspNetCore.Blazor.Components; 22 | using Microsoft.AspNetCore.Blazor.RenderTree; 23 | 24 | namespace BlazorPages.Pages 25 | { 26 | [Route("/dynamic-render-tree")] 27 | public class DynamicRenderTree : BlazorComponent 28 | { 29 | protected override void BuildRenderTree(RenderTreeBuilder builder) 30 | { 31 | builder.OpenElement(0, "table"); 32 | builder.OpenElement(1, "tbody"); 33 | 34 | for (var row = 0; row < 3; row++) 35 | { 36 | builder.OpenElement(2, "tr"); 37 | for (var col = 0; col < 3; col++) 38 | { 39 | builder.OpenElement(3, "td"); 40 | builder.AddAttribute(4, "class", "tictactoe-cell"); 41 | builder.CloseElement(); 42 | } 43 | 44 | builder.CloseElement(); 45 | } 46 | 47 | builder.CloseElement(); 48 | builder.CloseElement(); 49 | } 50 | } 51 | } 52 | ``` 53 | 54 | ## Render Fragments 55 | 56 | It is not necessary to build the entire component in C#. You can also dynamically generate fragments as shown in the following example. Note that the method creating the render fragment (delegate in `DynamicFragment`) is called whenever a rendering occurs, not just during component load. 57 | 58 | ```cs 59 | @page "/render-fragment" 60 | 61 |

Welcome

62 | 63 |

Lorem ipsum...

64 | 65 | 68 | 69 | @DynamicFragment 70 | 71 |

Lorem ipsum...

72 | 73 | @functions { 74 | private string dynamicContent = "This is a long text..."; 75 | 76 | protected override void OnInit() 77 | { 78 | DynamicFragment = builder => 79 | { 80 | // Make the text longer every time this delegate is called 81 | dynamicContent = dynamicContent.Replace("long", "long long"); 82 | 83 | builder.OpenElement(1, "p"); 84 | builder.AddContent(2, dynamicContent); 85 | builder.CloseElement(); 86 | }; 87 | } 88 | 89 | private Microsoft.AspNetCore.Blazor.RenderFragment DynamicFragment; 90 | } 91 | ``` 92 | -------------------------------------------------------------------------------- /content/pages/layouts.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "Layouts" 3 | weight = 50 4 | lastModifierDisplayName = "rainer@software-architects.at" 5 | date = 2018-04-03 6 | +++ 7 | 8 | {{% notice note %}} 9 | This content has been removed because the topic is covered in [Microsoft's Blazor documentation](https://docs.microsoft.com/en-us/aspnet/core/blazor/layouts). 10 | {{% /notice %}} 11 | -------------------------------------------------------------------------------- /content/pages/lifecycle-methods.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "Lifecycle Methods" 3 | weight = 10 4 | lastModifierDisplayName = "rainer@software-architects.at" 5 | date = 2018-04-29 6 | +++ 7 | 8 | {{% notice note %}} 9 | This content has been removed because the topic is covered in [Microsoft's Blazor documentation](https://docs.microsoft.com/en-us/aspnet/core/blazor/components#lifecycle-methods). 10 | {{% /notice %}} 11 | -------------------------------------------------------------------------------- /content/pages/router.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "Router" 3 | weight = 40 4 | lastModifierDisplayName = "rainer@software-architects.at" 5 | date = 2018-04-29 6 | +++ 7 | 8 | {{% notice note %}} 9 | {{% siteparam "disclaimer" %}} 10 | {{% /notice %}} 11 | 12 | ## Introduction 13 | 14 | Blazor comes with a client-side router (`Microsoft.AspNetCore.Blazor.Routing.Router`, see [source on GitHub](https://github.com/aspnet/Blazor/blob/release/0.1.0/src/Microsoft.AspNetCore.Blazor/Routing/Router.cs). At the time of writing, the router is quite limited compared to e.g. Angular's router. However, it already contains all you need to create basic web apps that consist of multiple pages. 15 | 16 | If you create a new Blazor app, the router is configured in *App.cshtml*: 17 | 18 | ```html 19 | 23 | 24 | ``` 25 | 26 | ## Route Templates 27 | 28 | The router looks for all classes that implement `Microsoft.AspNetCore.Blazor.Components.IComponent` in the assembly specified in *App.cshtml* (see above). Each component class has to have a `Microsoft.AspNetCore.Blazor.Components.RouteAttribute` that specifies the *route template*. In *.cshtml*, the attribute is set using `@page` (e.g. `@page "/hello-planet/{Planet}"`). If you implement a component without a template in pure C#, you have to use the attribute (e.g. `[RouteAttribute("/hello-planet/{Planet}")]`). Note that `@page` directives are turned into `RouteAttribute` attributes in the background by the Blazor template compiler. 29 | 30 | Here is an example for a simple template with a route template: 31 | 32 | ```cs 33 | @page "/hello-universe" 34 | 35 |

Hello Universe!

36 | ``` 37 | 38 | Components can have multiple routes on which they are available. If you need that, just add multiple `@page` directives or `RouteAttribute` attributes: 39 | 40 | ```cs 41 | @page "/" 42 | @page "/Page1" 43 |

Page1

44 | ``` 45 | 46 | Route templates can contain parameters. In `@page "/hello-planet/{Planet}"`, `{Planet}` would be such a parameter. Parameters are assigned to properties of the component. These properties must be annotated with the `ParameterAttribute`. Here is an example for a simple template with a route parameter: 47 | 48 | ```cs 49 | @page "/hello-planet/{Planet}" 50 | 51 |

Hello @Planet!

52 | 53 | @functions { 54 | [Parameter] 55 | public string Planet { get; set; } 56 | 57 | protected override void OnInit() 58 | { 59 | Console.WriteLine(Planet); 60 | } 61 | } 62 | ``` 63 | 64 | ## Links 65 | 66 | You can define *relative* links as usual with the HTML element `a`. The default Blazor templates adds a [`base` element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base) to `index.html`: ``. Therefore, relative links will not reload the entire page but will be handled by Blazor's router on the client-side. 67 | 68 | Blazor also comes with a helper class `NavLink` that can be used as an alternative. It automatically sets the `active` CSS class if the `href` matches the current URI. 69 | 70 | Here is an example of a simple menu with client-side links: 71 | 72 | ```html 73 | @page "/" 74 |

75 | Hello Universe
76 | Hello World
77 | Hello beautiful World
78 | Hello Moon
79 | Hello Planet Epsilon Eridani 80 |

81 | 82 |

83 | Hello Universe
84 |

85 | ``` 86 | 87 | Note that Blazor does not recreate a component if only the URL parameters change. If a user is in the sample shown above already on the page *HelloWorld* and clicks on the link *Hello beautiful World*, no new instance of the `HelloWorld` class is created. The existing class is reused instead. 88 | 89 | ## Accessing Query Parameters 90 | 91 | You can add query parameters using the `IUriHelper` [default service](https://learn-blazor.com/architecture/dependency-injection/#default-services). The following example shows how this can be done. Note that it requires referencing the NuGet package `Microsoft.AspNetCore.WebUtilities` in your *.csproj* file. 92 | 93 | ```cs 94 | @page "/hello-world" 95 | @implements IDisposable 96 | @inject Microsoft.AspNetCore.Blazor.Services.IUriHelper UriHelper 97 | 98 |

Hello @Type World!

99 | 100 | 101 | 102 | @functions { 103 | private string Type { get; set; } 104 | 105 | protected override void OnInit() 106 | { 107 | RefreshType(); 108 | UriHelper.OnLocationChanged += OnLocationChanges; 109 | } 110 | 111 | private void OnLocationChanges(object sender, LocationChangedEventArgs e) => RefreshType(); 112 | 113 | private void RefreshType() 114 | { 115 | var uri = new Uri(UriHelper.GetAbsoluteUri()); 116 | Type = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(uri.Query).TryGetValue("type", out var type) ? type.First() : ""; 117 | StateHasChanged(); 118 | } 119 | 120 | public void Dispose() 121 | { 122 | UriHelper.OnLocationChanged -= OnLocationChanges; 123 | } 124 | } 125 | ``` 126 | 127 | ## Navigate in Code 128 | 129 | `IUriHelper` can also be used to trigger navigation in code. The following example demonstrates how to navigate when the user clicks a button: 130 | 131 | ```cs 132 | @page "/navigate-in-code" 133 | @inject Microsoft.AspNetCore.Blazor.Services.IUriHelper UriHelper 134 | 135 | 136 | 137 | 138 | 139 | @functions { 140 | private void Navigate() 141 | { 142 | UriHelper.NavigateTo("/hello-world"); 143 | } 144 | } 145 | ``` 146 | -------------------------------------------------------------------------------- /content/terms-of-service.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "Terms of Service" 3 | +++ 4 | 5 | {{% notice note %}} 6 | {{% siteparam "disclaimer" %}} 7 | {{% /notice %}} 8 | 9 | ## Publisher 10 | 11 | **software architects gmbh**
12 | Birkenweg 16
13 | 4060 Leonding
14 | Austria 15 | 16 | +43 732 673575
17 | office@software-architects.at 18 | 19 | ## Terms 20 | 21 | By accessing this website, you are agreeing to be bound by these terms of service, all applicable laws and regulations, and agree that you are responsible for compliance with any applicable local laws. If you do not agree with any of these terms, you are prohibited from using or accessing this site. 22 | 23 | ## Use License 24 | 25 | This site's content is licensed under a [Attribution-NonCommercial-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-nc-sa/4.0/). 26 | 27 | ## Disclaimer 28 | 29 | > Blazor is available in a preview release. Be careful if you use it for production systems. 30 | 31 | The materials on this website are provided on an *as is* basis. The publisher makes no warranties, expressed or implied, and hereby disclaims and negates all other warranties including, without limitation, implied warranties or conditions of merchantability, fitness for a particular purpose, or non-infringement of intellectual property or other violation of rights. 32 | 33 | Further, the publisher does not warrant or make any representations concerning the accuracy, likely results, or reliability of the use of the materials on its website or otherwise relating to such materials or on any sites linked to this site. 34 | 35 | ## Limitations 36 | 37 | In no event shall the publisher or its suppliers be liable for any damages (including, without limitation, damages for loss of data or profit, or due to business interruption) arising out of the use or inability to use the materials on this website, even if the publisher or an authorized representative has been notified orally or in writing of the possibility of such damage. Because some jurisdictions do not allow limitations on implied warranties, or limitations of liability for consequential or incidental damages, these limitations may not apply to you. 38 | 39 | ## Accuracy of materials 40 | 41 | The materials appearing on this website could include technical, typographical, or photographic errors. The publisher does not warrant that any of the materials on its website are accurate, complete or current. The publisher may make changes to the materials contained on its website at any time without notice. However, the publisher does not make any commitment to update the materials. 42 | 43 | ## Links 44 | 45 | The publisher has not reviewed all of the sites linked to its website and is not responsible for the contents of any such linked site. The inclusion of any link does not imply endorsement by the publisher of the site. Use of any such linked website is at the user's own risk. 46 | 47 | ## Modifications 48 | 49 | The publisher may revise these terms of service for its website at any time without notice. By using this website you are agreeing to be bound by the then current version of these terms of service. 50 | 51 | ## Governing Law 52 | 53 | These terms and conditions are governed by and construed in accordance with the laws of Austria and you irrevocably submit to the exclusive jurisdiction of the courts in that State or location. 54 | -------------------------------------------------------------------------------- /layouts/partials/footer.html: -------------------------------------------------------------------------------- 1 |
2 | 20 | 21 | 22 | {{ if .Params.chapter }} 23 | 24 | {{ end }} 25 | 26 | {{ partial "custom-comments.html" . }} 27 | 28 | 29 | 71 | 72 | 73 | 74 |
75 |
76 |
77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 94 | {{ partial "custom-footer.html" . }} 95 | 96 | 97 | -------------------------------------------------------------------------------- /layouts/partials/logo.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Learn Blazor 2 | 3 | ## Introduction 4 | 5 | This repository contained the source code for [learn-blazor.com](https://learn-blazor.com/). 6 | 7 | **NOTE THAT THIS REPOSITORY IS NO LONGER MAINTAINED**. I started the *learn-blazor* project when absolutely not official docs were available from Microsoft. Now that Microsoft has pretty good docs (I had the honor to contribute a little bit to it, too), *learn-blazor* is no longer necessary. 8 | 9 | I hope you continue to have fun with Blazor! 10 | -------------------------------------------------------------------------------- /samples/.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | obj 3 | .vs 4 | *.csproj.user 5 | launchSettings.json 6 | PublishProfiles 7 | out 8 | -------------------------------------------------------------------------------- /samples/BlazorPages/App.cshtml: -------------------------------------------------------------------------------- 1 |  5 | 6 | -------------------------------------------------------------------------------- /samples/BlazorPages/BlazorPages.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | dotnet 6 | blazor serve 7 | 7.3 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /samples/BlazorPages/BlazorPages.csproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | ProjectDebugger 5 | 6 | 7 | BlazorPages 8 | 9 | -------------------------------------------------------------------------------- /samples/BlazorPages/BlazorPages.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27428.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorPages", "BlazorPages.csproj", "{F3381F4D-B5C8-4FF1-9D8B-CF07F55DF9EA}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {F3381F4D-B5C8-4FF1-9D8B-CF07F55DF9EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {F3381F4D-B5C8-4FF1-9D8B-CF07F55DF9EA}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {F3381F4D-B5C8-4FF1-9D8B-CF07F55DF9EA}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {F3381F4D-B5C8-4FF1-9D8B-CF07F55DF9EA}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {3234EBA9-E587-446E-A34F-41DDE1D87364} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /samples/BlazorPages/Pages/ChildComponent.cshtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | @functions { 4 | [Parameter] 5 | private Action OnSomeEvent { get; set; } 6 | 7 | private void OnClick() 8 | { 9 | OnSomeEvent?.Invoke(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /samples/BlazorPages/Pages/DynamicRenderTree.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Blazor.Components; 2 | using Microsoft.AspNetCore.Blazor.RenderTree; 3 | 4 | namespace BlazorPages.Pages 5 | { 6 | [Route("/dynamic-render-tree")] 7 | public class DynamicRenderTree : BlazorComponent 8 | { 9 | protected override void BuildRenderTree(RenderTreeBuilder builder) 10 | { 11 | builder.OpenElement(0, "table"); 12 | builder.OpenElement(1, "tbody"); 13 | 14 | for (var row = 0; row < 3; row++) 15 | { 16 | builder.OpenElement(2, "tr"); 17 | for (var col = 0; col < 3; col++) 18 | { 19 | builder.OpenElement(3, "td"); 20 | builder.AddAttribute(4, "class", "tictactoe-cell"); 21 | builder.CloseElement(); 22 | } 23 | 24 | builder.CloseElement(); 25 | } 26 | 27 | builder.CloseElement(); 28 | builder.CloseElement(); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /samples/BlazorPages/Pages/EventBinding.cshtml: -------------------------------------------------------------------------------- 1 | @page "/event-binding" 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | @functions { 13 | public string Hello { get; set; } = "Hello"; 14 | 15 | private void Clicked() 16 | { 17 | Console.WriteLine("Hello World"); 18 | } 19 | 20 | public void ChildEventClicked() 21 | { 22 | Console.WriteLine("Child event clicked"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /samples/BlazorPages/Pages/Initialization.cshtml: -------------------------------------------------------------------------------- 1 | 

Hello, @Name!

2 | 3 |
    4 | @foreach(var item in log) 5 | { 6 |
  • @item
  • 7 | } 8 |
9 | 10 | @functions { 11 | // Parameter that can be set by parent component 12 | [Parameter] 13 | private string Name { get; set; } 14 | 15 | private List log = new List(); 16 | private void Log(string message) => log.Add($"{DateTime.UtcNow.ToString("O")} - {message}"); 17 | 18 | protected override void OnInit() => Log("OnInit"); 19 | 20 | protected override async Task OnInitAsync() 21 | { 22 | Log("OnInitAsync starting"); 23 | 24 | // Simulate async initialization work 25 | await Task.Delay(1000); 26 | 27 | Log("OnInitAsync finished"); 28 | } 29 | 30 | protected override void OnParametersSet() => Log("OnParametersSet"); 31 | } 32 | -------------------------------------------------------------------------------- /samples/BlazorPages/Pages/InitializationParent.cshtml: -------------------------------------------------------------------------------- 1 | @page "/initialization-parent" 2 | 3 |

Page with Parameters

4 | 5 | 6 | 7 | 8 | 9 | @functions { 10 | private string Name { get; set; } = "Tom"; 11 | private void SwitchName() => Name = Name == "Tom" ? "John" : "Tom"; 12 | } 13 | -------------------------------------------------------------------------------- /samples/BlazorPages/Pages/ManualRefresh.cshtml: -------------------------------------------------------------------------------- 1 | @page "/manual-refresh" 2 | @using System.Threading; 3 | 4 |

@Count

5 | 6 | 7 | 8 | @functions { 9 | private int Count { get; set; } = 10; 10 | 11 | void StartCountdown() 12 | { 13 | var timer = new Timer(new TimerCallback(_ => 14 | { 15 | if (Count > 0) 16 | { 17 | Count--; 18 | 19 | // Note that the following line is necessary because otherwise 20 | // Blazor would not recognize the state change and not refresh the UI 21 | this.StateHasChanged(); 22 | } 23 | }), null, 1000, 1000); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /samples/BlazorPages/Pages/ManualRefreshChild.cshtml: -------------------------------------------------------------------------------- 1 | @using System.Threading; 2 | @using System.Collections.Generic 3 | 4 |

Child: @CurrentValue

5 | 6 |
7 |

You requested @NumberOfElements elements. Here they are:

8 |
    9 | @foreach (var n in Numbers) 10 | { 11 |
  • @n
  • 12 | } 13 |
14 | 15 | @functions { 16 | private int CurrentValue { get; set; } 17 | 18 | [Parameter] 19 | private int NumberOfElements { get; set; } 20 | 21 | private IEnumerable Numbers 22 | { 23 | get 24 | { 25 | for (var i = 0; i < NumberOfElements; i++) 26 | { 27 | yield return i; 28 | } 29 | } 30 | } 31 | 32 | protected override void OnInit() 33 | { 34 | var timer = new Timer(new TimerCallback(_ => 35 | { 36 | CurrentValue++; 37 | 38 | // Note that we have to manually refresh the UI although 39 | // our parent component calls `StateHasChanged`, too. The 40 | // parent's call does not auto-refresh its children. 41 | StateHasChanged(); 42 | }), null, 1000, 1000); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /samples/BlazorPages/Pages/ManualRefreshParent.cshtml: -------------------------------------------------------------------------------- 1 | @page "/manual-refresh-parent" 2 | @using System.Threading; 3 | 4 |

Parent: @CurrentValue

5 | 6 | 7 | @functions { 8 | private int CurrentValue { get; set; } 9 | 10 | protected override void OnInit() 11 | { 12 | var timer = new Timer(new TimerCallback(_ => 13 | { 14 | CurrentValue++; 15 | 16 | // Note that we have to manually refresh the UI although 17 | // our child component calls `StateHasChanged`, too. The 18 | // child's call does not auto-refresh its parents. 19 | StateHasChanged(); 20 | }), null, 5000, 5000); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /samples/BlazorPages/Pages/OneWayDataBinding.cshtml: -------------------------------------------------------------------------------- 1 | @page "/one-way-data-binding" 2 | 3 | 4 | 5 | 6 | 7 | 11 |

Counter: @Count

12 | 13 | 17 | @if (ShowWarning) 18 | { 19 |

Warning!

20 | } 21 | 22 | 27 | 28 | 29 | 33 |

Notification

34 | 35 | 40 |

This is a note

41 |

This is a note

42 | 43 | 47 |
    48 | @foreach (var number in Numbers) 49 | { 50 |
  • @number
  • 51 | } 52 |
53 | 54 | @functions { 55 | private int Count { get; set; } = 0; 56 | private bool ShowWarning { get; set; } = true; 57 | private string Background { get; set; } = "red"; 58 | private bool NoteIsActive { get; set; } = true; 59 | private List Numbers { get; set; } = new List { 1, 2, 3 }; 60 | private string HighlightClass { get; set; } = "highlight"; 61 | 62 | private void ChangeValues() 63 | { 64 | Count++; 65 | ShowWarning = !ShowWarning; 66 | HighlightClass = HighlightClass == null ? "highlight" : null; 67 | Background = Background == "red" ? "green" : "red"; 68 | NoteIsActive = !NoteIsActive; 69 | Numbers.Add(Numbers.Max() + 1); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /samples/BlazorPages/Pages/RenderFragment.cshtml: -------------------------------------------------------------------------------- 1 | @page "/render-fragment" 2 | 3 |

Welcome

4 | 5 |

Lorem ipsum...

6 | 7 | 10 | 11 | @DynamicFragment 12 | 13 |

Lorem ipsum...

14 | 15 | @functions { 16 | private string dynamicContent = "This is a long text..."; 17 | 18 | protected override void OnInit() 19 | { 20 | DynamicFragment = builder => 21 | { 22 | // Make the text longer every time this delegate is called 23 | dynamicContent = dynamicContent.Replace("long", "long long"); 24 | 25 | builder.OpenElement(1, "p"); 26 | builder.AddContent(2, dynamicContent); 27 | builder.CloseElement(); 28 | }; 29 | } 30 | 31 | private Microsoft.AspNetCore.Blazor.RenderFragment DynamicFragment; 32 | } 33 | -------------------------------------------------------------------------------- /samples/BlazorPages/Pages/TwoWayDataBinding.cshtml: -------------------------------------------------------------------------------- 1 | @page "/two-way-data-binding" 2 | 3 |

4 | @* You can bind using @Property or @Field *@ 5 | Enter your name:
6 | 7 | @* Alternatively also using "Property" or "Field" *@ 8 | What is your age?
9 | 10 | @* Note how to pass a format for DateTime *@ 11 | What is your birthday (culture-invariant default format)?
12 | What is your birthday (German date format)?
13 | 14 | @* Data binding for checkboxes with boolean properties *@ 15 | Are you an administrator?
16 | 17 | @* Data binding for selects with enums *@ 18 | 23 | 24 | @* 25 | The following line would not work because decimal is not supported 26 | What is your salery?
27 | *@ 28 |

29 | 30 |

Hello, @Name!

31 | 32 |

You are @Age years old. You are born on the @Birthday. You are @TypeOfEmployee.

33 | 34 | @if (IsAdmin) 35 | { 36 |

You are an administrator!

37 | } 38 | 39 | @functions { 40 | private enum EmployeeType { Employee, Contractor, Intern }; 41 | private EmployeeType TypeOfEmployee { get; set; } = EmployeeType.Employee; 42 | 43 | private string Name { get; set; } = "Tom"; 44 | private bool IsAdmin { get; set; } = true; 45 | private int Age { get; set; } = 33; 46 | public DateTime Birthday { get; set; } = DateTime.Today; 47 | 48 | public decimal Salary { get; set; } 49 | } 50 | -------------------------------------------------------------------------------- /samples/BlazorPages/Pages/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @layout MainLayout 2 | -------------------------------------------------------------------------------- /samples/BlazorPages/Pages/menu.cshtml: -------------------------------------------------------------------------------- 1 | @page "/" 2 | 3 |

Blazor Pages Sample

4 | 5 | One-Way Data Binding
6 | Two-Way Data Binding
7 | Initialization
8 | Event Binding
9 | Manual Refresh
10 | Manual Refresh (Parent/Child)
11 | Render Fragment
12 | Dynamic Render Tree
13 | -------------------------------------------------------------------------------- /samples/BlazorPages/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Blazor.Hosting; 2 | 3 | namespace BlazorPages 4 | { 5 | public class Program 6 | { 7 | public static void Main(string[] args) 8 | { 9 | CreateHostBuilder(args).Build().Run(); 10 | } 11 | 12 | public static IWebAssemblyHostBuilder CreateHostBuilder(string[] args) => 13 | BlazorWebAssemblyHost.CreateDefaultBuilder() 14 | .UseBlazorStartup(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/BlazorPages/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:64053/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "BlazorPages": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "http://localhost:64054/" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /samples/BlazorPages/Shared/MainLayout.cshtml: -------------------------------------------------------------------------------- 1 | @inherits BlazorLayoutComponent 2 | 3 | @Body 4 | -------------------------------------------------------------------------------- /samples/BlazorPages/Startup.cs: -------------------------------------------------------------------------------- 1 | using BlazorPages; 2 | using Microsoft.AspNetCore.Blazor.Builder; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace BlazorPages 6 | { 7 | public class Startup 8 | { 9 | public void ConfigureServices(IServiceCollection services) 10 | { 11 | } 12 | 13 | public void Configure(IBlazorApplicationBuilder app) 14 | { 15 | app.AddComponent("app"); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /samples/BlazorPages/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using Microsoft.AspNetCore.Blazor 3 | @using Microsoft.AspNetCore.Blazor.Components 4 | @using Microsoft.AspNetCore.Blazor.Layouts 5 | @using Microsoft.AspNetCore.Blazor.Routing 6 | @using Microsoft.JSInterop 7 | @using BlazorPages 8 | @using BlazorPages.Shared 9 | -------------------------------------------------------------------------------- /samples/BlazorPages/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | .note { 2 | padding: 5px; 3 | } 4 | 5 | .highlight { 6 | background-color: yellow; 7 | } 8 | 9 | .tictactoe-cell { 10 | width: 50px; 11 | height: 50px; 12 | border: 2px solid black; 13 | } 14 | -------------------------------------------------------------------------------- /samples/BlazorPages/wwwroot/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | learn-blazor.com || Blazor Pages 6 | 7 | 8 | 9 | 10 | Loading... 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /samples/DependencyInjection/App.cshtml: -------------------------------------------------------------------------------- 1 |  5 | 6 | -------------------------------------------------------------------------------- /samples/DependencyInjection/DependencyInjection.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | dotnet 6 | blazor serve 7 | 7.3 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /samples/DependencyInjection/DependencyInjection.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27428.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DependencyInjection", "DependencyInjection.csproj", "{F3381F4D-B5C8-4FF1-9D8B-CF07F55DF9EA}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {F3381F4D-B5C8-4FF1-9D8B-CF07F55DF9EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {F3381F4D-B5C8-4FF1-9D8B-CF07F55DF9EA}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {F3381F4D-B5C8-4FF1-9D8B-CF07F55DF9EA}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {F3381F4D-B5C8-4FF1-9D8B-CF07F55DF9EA}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {3234EBA9-E587-446E-A34F-41DDE1D87364} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /samples/DependencyInjection/Pages/CustomerList.cshtml: -------------------------------------------------------------------------------- 1 | @page "/" 2 | @using DependencyInjection.Services 3 | @inject IRepository Repository 4 | 5 |
    6 | @if (Customers != null) 7 | { 8 | @foreach (var customer in Customers) 9 | { 10 |
  • @customer.FirstName @customer.LastName
  • 11 | } 12 | } 13 |
14 | 15 | Lifetime demo 16 | 17 | @functions { 18 | private IReadOnlyList Customers; 19 | 20 | protected override async Task OnInitAsync() 21 | { 22 | Customers = await Repository.GetAllCustomersAsync(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /samples/DependencyInjection/Pages/Lifetime1.cshtml: -------------------------------------------------------------------------------- 1 | @page "/lifetime1" 2 | @inject MyTransientService Transient 3 | @inject MySingletonService Singleton 4 | @inject MyScopedService Scoped 5 | 6 |
    7 |
  • @Singleton.Name @Singleton.GetHashCode()
  • 8 |
  • @Transient.Name @Transient.GetHashCode()
  • 9 |
  • @Scoped.Name @Scoped.GetHashCode()
  • 10 |
11 | 12 | Other lifetime demo page 13 | Same lifetime demo page with params 14 | 15 | @functions { 16 | } 17 | -------------------------------------------------------------------------------- /samples/DependencyInjection/Pages/Lifetime2.cshtml: -------------------------------------------------------------------------------- 1 | @page "/lifetime2" 2 | @inject MyTransientService Transient 3 | @inject MySingletonService Singleton 4 | @inject MyScopedService Scoped 5 | 6 |
    7 |
  • @Singleton.Name @Singleton.GetHashCode()
  • 8 |
  • @Transient.Name @Transient.GetHashCode()
  • 9 |
  • @Scoped.Name @Scoped.GetHashCode()
  • 10 |
11 | 12 | Other lifetime demo page 13 | 14 | @functions { 15 | } 16 | -------------------------------------------------------------------------------- /samples/DependencyInjection/Pages/LifetimeDemo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DependencyInjection.Pages 4 | { 5 | public class MyService 6 | { 7 | public MyService(string name) { this.Name = name; } 8 | public string Name { get; } 9 | } 10 | 11 | public class MySingletonService : MyService { public MySingletonService() : base("Singleton") { } } 12 | public class MyTransientService : MyService { public MyTransientService() : base("Transient") { } } 13 | public class MyScopedService : MyService { public MyScopedService() : base("Scoped") { } } 14 | } 15 | -------------------------------------------------------------------------------- /samples/DependencyInjection/Pages/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @layout MainLayout 2 | -------------------------------------------------------------------------------- /samples/DependencyInjection/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Blazor.Hosting; 2 | 3 | namespace DependencyInjection 4 | { 5 | public class Program 6 | { 7 | public static void Main(string[] args) 8 | { 9 | CreateHostBuilder(args).Build().Run(); 10 | } 11 | 12 | public static IWebAssemblyHostBuilder CreateHostBuilder(string[] args) => 13 | BlazorWebAssemblyHost.CreateDefaultBuilder() 14 | .UseBlazorStartup(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/DependencyInjection/Services/Repository.cs: -------------------------------------------------------------------------------- 1 | using DependencyInjection.Pages; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Net.Http; 5 | using System.Threading.Tasks; 6 | 7 | namespace DependencyInjection.Services 8 | { 9 | public class Customer 10 | { 11 | public string FirstName { get; set; } 12 | 13 | public string LastName { get; set; } 14 | } 15 | 16 | public interface IRepository 17 | { 18 | Task> GetAllCustomersAsync(); 19 | } 20 | 21 | public class Repository : IRepository 22 | { 23 | private static Customer[] Customers { get; set; } = new[] 24 | { 25 | new Customer { FirstName = "Foo", LastName = "Bar" }, 26 | new Customer { FirstName = "John", LastName = "Doe" } 27 | }; 28 | 29 | // Note that the constructor gets an HttpClient via dependency 30 | // injection. HttpClient is a default service offered by Blazor. 31 | // Note that additional parameters are ok if you specify default 32 | // value for them. 33 | public Repository(HttpClient client, string something = "dummy") 34 | { 35 | // In practice, we would store the HttpClient and use it 36 | // to get customers via e.g. a RESTful Web API 37 | } 38 | 39 | public async Task> GetAllCustomersAsync() 40 | { 41 | // Simulate some long running, async work (e.g. web request) 42 | await Task.Delay(100); 43 | 44 | return Repository.Customers; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /samples/DependencyInjection/Shared/MainLayout.cshtml: -------------------------------------------------------------------------------- 1 | @inherits BlazorLayoutComponent 2 | 3 | @Body 4 | -------------------------------------------------------------------------------- /samples/DependencyInjection/Startup.cs: -------------------------------------------------------------------------------- 1 | using DependencyInjection.Pages; 2 | using DependencyInjection.Services; 3 | using Microsoft.AspNetCore.Blazor.Builder; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | namespace DependencyInjection 7 | { 8 | public class Startup 9 | { 10 | public void ConfigureServices(IServiceCollection services) 11 | { 12 | services.AddTransient(); 13 | services.AddTransient(); 14 | services.AddScoped(); 15 | services.AddSingleton(); 16 | } 17 | 18 | public void Configure(IBlazorApplicationBuilder app) 19 | { 20 | app.AddComponent("app"); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /samples/DependencyInjection/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using Microsoft.AspNetCore.Blazor 3 | @using Microsoft.AspNetCore.Blazor.Components 4 | @using Microsoft.AspNetCore.Blazor.Layouts 5 | @using Microsoft.AspNetCore.Blazor.Routing 6 | @using DependencyInjection 7 | @using DependencyInjection.Shared 8 | -------------------------------------------------------------------------------- /samples/DependencyInjection/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | .note { 2 | padding: 5px; 3 | } 4 | 5 | .highlight { 6 | background-color: yellow; 7 | } 8 | 9 | .tictactoe-cell { 10 | width: 50px; 11 | height: 50px; 12 | border: 2px solid black; 13 | } 14 | -------------------------------------------------------------------------------- /samples/DependencyInjection/wwwroot/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | learn-blazor.com || Blazor Pages 6 | 7 | 8 | 9 | 10 | Loading... 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /samples/RestApi/RestApi.Client/App.cshtml: -------------------------------------------------------------------------------- 1 |  5 | 6 | -------------------------------------------------------------------------------- /samples/RestApi/RestApi.Client/Pages/FetchData.cshtml: -------------------------------------------------------------------------------- 1 | @page "/" 2 | @using RestApi.Shared 3 | @inject HttpClient Http 4 | 5 |

Customer List

6 | 7 |

This component demonstrates fetching data from the server.

8 | 9 | 10 | 11 | 12 | 13 | @if (Customers == null) 14 | { 15 |

Loading...

16 | } 17 | else 18 | { 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | @foreach (var customer in Customers) 30 | { 31 | 32 | 33 | 34 | 35 | 36 | 37 | } 38 | 39 |
IDFirst NameLast NameDepartment
@customer.ID@customer.FirstName@customer.LastName@customer.Department
40 | } 41 | @functions { 42 | private Customer[] Customers { get; set; } 43 | 44 | protected override async Task OnInitAsync() 45 | { 46 | await RefreshCustomerList(); 47 | } 48 | 49 | private async Task RefreshCustomerList() 50 | { 51 | Customers = await Http.GetJsonAsync("/api/Customer"); 52 | StateHasChanged(); 53 | } 54 | 55 | private async Task FillWithDemoData() 56 | { 57 | for (var i = 0; i < 10; i++) 58 | { 59 | await Http.SendJsonAsync(HttpMethod.Post, "/api/Customer", new Customer 60 | { 61 | FirstName = "Tom", 62 | LastName = $"Customer {i}", 63 | Department = i % 2 == 0 ? "Sales" : "Research" 64 | }); 65 | } 66 | 67 | await RefreshCustomerList(); 68 | } 69 | 70 | private async Task DeleteAllCustomers() 71 | { 72 | foreach (var c in Customers) 73 | { 74 | await Http.DeleteAsync($"/api/Customer/{c.ID}"); 75 | } 76 | 77 | await RefreshCustomerList(); 78 | } 79 | 80 | private async Task PrintWebApiResponse() 81 | { 82 | var response = await Http.GetStringAsync("/api/Customer"); 83 | Console.WriteLine(response); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /samples/RestApi/RestApi.Client/Pages/InteropBasics.cshtml: -------------------------------------------------------------------------------- 1 | @page "/interop-basics" 2 | @using Microsoft.JSInterop; 3 | @using RestApi.Shared 4 | @inject HttpClient Http 5 | 6 |

Interop Basics

7 | 8 | 9 | 10 | @functions { 11 | private async void CallJS() 12 | { 13 | // Simple function call with a basic data type 14 | if (await JSRuntime.Current.InvokeAsync("say", "Hello")) 15 | { 16 | // This line will be reached as our `say` function returns true 17 | Console.WriteLine("Returned true"); 18 | } 19 | 20 | // Call our function with an object. It will be serialized (JSON), 21 | // passed to JS-part of Blazor and deserialized into a JavaScript 22 | // object again. 23 | await JSRuntime.Current.InvokeAsync("say", new { greeting = "Hello" }); 24 | 25 | // Get some demo data from a web service and pass it to our function. 26 | // Again, it will be turned to JSON and back during the function call. 27 | var customers = await Http.GetJsonAsync>("/api/Customer"); 28 | await JSRuntime.Current.InvokeAsync("say", customers); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /samples/RestApi/RestApi.Client/Pages/KendoAutocomplete.cshtml: -------------------------------------------------------------------------------- 1 | @page "/auto-complete" 2 | @using RestApi.Shared 3 | @using Microsoft.JSInterop 4 | @using System.Linq 5 | @inject HttpClient Http 6 | 7 |

Autocomplete Demo

8 | 9 | 10 | 11 | @functions { 12 | protected override async Task OnInitAsync() 13 | { 14 | await RefreshCustomerList(); 15 | } 16 | 17 | private async Task RefreshCustomerList() 18 | { 19 | // Call a web api to get some data 20 | var customers = await Http.GetJsonAsync>("/api/Customer"); 21 | 22 | // Here you can execute any C# business logic. For demo purposes, we 23 | // project the result into a list of strings. 24 | var projectedCustomers = customers 25 | .Select(c => $"{c.LastName}, {c.FirstName}") 26 | .ToList(); 27 | 28 | await JSRuntime.Current.InvokeAsync("fillAutocomplete", projectedCustomers); 29 | } 30 | 31 | [JSInvokable] 32 | public static void SelectCustomer(string customerName) 33 | { 34 | Console.WriteLine($"Select customer '{customerName}'"); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /samples/RestApi/RestApi.Client/Pages/StringUtil.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.JSInterop; 2 | using System.Threading.Tasks; 3 | 4 | namespace RestApi.Client.Pages 5 | { 6 | public static class StringUtil 7 | { 8 | [JSInvokable] 9 | public static Task Concat(string str1, string str2, string str3) 10 | { 11 | return Task.FromResult(string.Concat(str1, str2, str3)); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /samples/RestApi/RestApi.Client/Pages/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @layout MainLayout 2 | -------------------------------------------------------------------------------- /samples/RestApi/RestApi.Client/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Blazor.Hosting; 2 | 3 | namespace RestApi.Client 4 | { 5 | public class Program 6 | { 7 | public static void Main(string[] args) 8 | { 9 | CreateHostBuilder(args).Build().Run(); 10 | } 11 | 12 | public static IWebAssemblyHostBuilder CreateHostBuilder(string[] args) => 13 | BlazorWebAssemblyHost.CreateDefaultBuilder() 14 | .UseBlazorStartup(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/RestApi/RestApi.Client/RestApi.Client.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | Exe 6 | 7.3 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /samples/RestApi/RestApi.Client/Shared/MainLayout.cshtml: -------------------------------------------------------------------------------- 1 | @inherits BlazorLayoutComponent 2 | 3 |
4 | @Body 5 |
6 | -------------------------------------------------------------------------------- /samples/RestApi/RestApi.Client/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Blazor.Builder; 2 | using Microsoft.Extensions.DependencyInjection; 3 | 4 | namespace RestApi.Client 5 | { 6 | public class Startup 7 | { 8 | public void ConfigureServices(IServiceCollection services) 9 | { 10 | } 11 | 12 | public void Configure(IBlazorApplicationBuilder app) 13 | { 14 | app.AddComponent("app"); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /samples/RestApi/RestApi.Client/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using Microsoft.AspNetCore.Blazor 3 | @using Microsoft.AspNetCore.Blazor.Components 4 | @using Microsoft.AspNetCore.Blazor.Layouts 5 | @using Microsoft.AspNetCore.Blazor.Routing 6 | @using RestApi.Client 7 | @using RestApi.Client.Shared 8 | -------------------------------------------------------------------------------- /samples/RestApi/RestApi.Client/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin-top: 25px; 3 | } 4 | -------------------------------------------------------------------------------- /samples/RestApi/RestApi.Client/wwwroot/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | REST API 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | Loading... 18 | 19 | 20 | 21 | 22 | 23 | 24 | 38 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /samples/RestApi/RestApi.Server/Controllers/CustomerController.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.EntityFrameworkCore; 6 | using RestApi.Server.Data; 7 | using RestApi.Shared; 8 | 9 | namespace RestApi.Server.Controllers 10 | { 11 | // Simple customer controller generated with ASP.NET Core scaffolding 12 | 13 | [Produces("application/json")] 14 | [Route("api/Customer")] 15 | public class CustomerController : Controller 16 | { 17 | private readonly CustomerContext _context; 18 | 19 | public CustomerController(CustomerContext context) 20 | { 21 | _context = context; 22 | } 23 | 24 | // GET: api/Customer 25 | [HttpGet] 26 | public IEnumerable GetCustomers() 27 | { 28 | return _context.Customers; 29 | } 30 | 31 | // GET: api/Customer/5 32 | [HttpGet("{id}")] 33 | public async Task GetCustomer([FromRoute] int id) 34 | { 35 | if (!ModelState.IsValid) 36 | { 37 | return BadRequest(ModelState); 38 | } 39 | 40 | var customer = await _context.Customers.SingleOrDefaultAsync(m => m.ID == id); 41 | 42 | if (customer == null) 43 | { 44 | return NotFound(); 45 | } 46 | 47 | return Ok(customer); 48 | } 49 | 50 | // PUT: api/Customer/5 51 | [HttpPut("{id}")] 52 | public async Task PutCustomer([FromRoute] int id, [FromBody] Customer customer) 53 | { 54 | if (!ModelState.IsValid) 55 | { 56 | return BadRequest(ModelState); 57 | } 58 | 59 | if (id != customer.ID) 60 | { 61 | return BadRequest(); 62 | } 63 | 64 | _context.Entry(customer).State = EntityState.Modified; 65 | 66 | try 67 | { 68 | await _context.SaveChangesAsync(); 69 | } 70 | catch (DbUpdateConcurrencyException) 71 | { 72 | if (!CustomerExists(id)) 73 | { 74 | return NotFound(); 75 | } 76 | else 77 | { 78 | throw; 79 | } 80 | } 81 | 82 | return NoContent(); 83 | } 84 | 85 | // POST: api/Customer 86 | [HttpPost] 87 | public async Task PostCustomer([FromBody] Customer customer) 88 | { 89 | if (!ModelState.IsValid) 90 | { 91 | return BadRequest(ModelState); 92 | } 93 | 94 | _context.Customers.Add(customer); 95 | await _context.SaveChangesAsync(); 96 | 97 | return CreatedAtAction("GetCustomer", new { id = customer.ID }, customer); 98 | } 99 | 100 | // DELETE: api/Customer/5 101 | [HttpDelete("{id}")] 102 | public async Task DeleteCustomer([FromRoute] int id) 103 | { 104 | if (!ModelState.IsValid) 105 | { 106 | return BadRequest(ModelState); 107 | } 108 | 109 | var customer = await _context.Customers.SingleOrDefaultAsync(m => m.ID == id); 110 | if (customer == null) 111 | { 112 | return NotFound(); 113 | } 114 | 115 | _context.Customers.Remove(customer); 116 | await _context.SaveChangesAsync(); 117 | 118 | return Ok(customer); 119 | } 120 | 121 | private bool CustomerExists(int id) 122 | { 123 | return _context.Customers.Any(e => e.ID == id); 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /samples/RestApi/RestApi.Server/Data/CustomerContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using RestApi.Shared; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace RestApi.Server.Data 9 | { 10 | public class CustomerContext : DbContext 11 | { 12 | public CustomerContext(DbContextOptions options) : base(options) { } 13 | 14 | public DbSet Customers { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/RestApi/RestApi.Server/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using Microsoft.AspNetCore; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.Extensions.Configuration; 7 | 8 | namespace RestApi.Server 9 | { 10 | public class Program 11 | { 12 | public static void Main(string[] args) 13 | { 14 | BuildWebHost(args).Run(); 15 | } 16 | 17 | public static IWebHost BuildWebHost(string[] args) => 18 | WebHost.CreateDefaultBuilder(args) 19 | .UseUrls("http://*:5000") 20 | .UseConfiguration(new ConfigurationBuilder() 21 | .AddCommandLine(args) 22 | .Build()) 23 | .UseStartup() 24 | .Build(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /samples/RestApi/RestApi.Server/RestApi.Server.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.1 5 | 7.3 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /samples/RestApi/RestApi.Server/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Blazor.Server; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.AspNetCore.Hosting; 4 | using Microsoft.AspNetCore.ResponseCompression; 5 | using Microsoft.EntityFrameworkCore; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Newtonsoft.Json.Serialization; 8 | using RestApi.Server.Data; 9 | using System.Linq; 10 | using System.Net.Mime; 11 | 12 | namespace RestApi.Server 13 | { 14 | public class Startup 15 | { 16 | public void ConfigureServices(IServiceCollection services) 17 | { 18 | // Use Entity Framework in-memory provider for this sample 19 | services.AddDbContext(options => options.UseInMemoryDatabase("Customers")); 20 | 21 | services.AddMvc().AddJsonOptions(options => 22 | { 23 | options.SerializerSettings.ContractResolver = new DefaultContractResolver(); 24 | }); 25 | 26 | services.AddResponseCompression(options => 27 | { 28 | options.MimeTypes = ResponseCompressionDefaults.MimeTypes 29 | .Concat(new[] { MediaTypeNames.Application.Octet, WasmMediaTypeNames.Application.Wasm }); 30 | }); 31 | } 32 | 33 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 34 | { 35 | if (env.IsDevelopment()) 36 | { 37 | app.UseDeveloperExceptionPage(); 38 | } 39 | 40 | app.UseMvc(); 41 | 42 | app.UseBlazor(); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /samples/RestApi/RestApi.Shared/Customer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace RestApi.Shared 6 | { 7 | public class Customer 8 | { 9 | public int ID { get; set; } 10 | 11 | public string FirstName { get; set; } 12 | 13 | public string LastName { get; set; } 14 | 15 | public string Department { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /samples/RestApi/RestApi.Shared/RestApi.Shared.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /samples/RestApi/RestApi.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio 15 3 | VisualStudioVersion = 15.0.27130.2027 4 | MinimumVisualStudioVersion = 15.0.26124.0 5 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RestApi.Server", "RestApi.Server\RestApi.Server.csproj", "{0DDD48DC-8D9E-4665-8A08-E91545D6194B}" 6 | EndProject 7 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RestApi.Client", "RestApi.Client\RestApi.Client.csproj", "{16A64EBF-BE40-454F-9871-9D60556AA663}" 8 | EndProject 9 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RestApi.Shared", "RestApi.Shared\RestApi.Shared.csproj", "{53B90B0B-61C5-4F25-BF2D-B51B8903D37C}" 10 | EndProject 11 | Global 12 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 13 | Debug|Any CPU = Debug|Any CPU 14 | Debug|x64 = Debug|x64 15 | Debug|x86 = Debug|x86 16 | Release|Any CPU = Release|Any CPU 17 | Release|x64 = Release|x64 18 | Release|x86 = Release|x86 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {16A64EBF-BE40-454F-9871-9D60556AA663}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {16A64EBF-BE40-454F-9871-9D60556AA663}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {16A64EBF-BE40-454F-9871-9D60556AA663}.Debug|x64.ActiveCfg = Debug|Any CPU 24 | {16A64EBF-BE40-454F-9871-9D60556AA663}.Debug|x64.Build.0 = Debug|Any CPU 25 | {16A64EBF-BE40-454F-9871-9D60556AA663}.Debug|x86.ActiveCfg = Debug|Any CPU 26 | {16A64EBF-BE40-454F-9871-9D60556AA663}.Debug|x86.Build.0 = Debug|Any CPU 27 | {16A64EBF-BE40-454F-9871-9D60556AA663}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {16A64EBF-BE40-454F-9871-9D60556AA663}.Release|Any CPU.Build.0 = Release|Any CPU 29 | {16A64EBF-BE40-454F-9871-9D60556AA663}.Release|x64.ActiveCfg = Release|Any CPU 30 | {16A64EBF-BE40-454F-9871-9D60556AA663}.Release|x64.Build.0 = Release|Any CPU 31 | {16A64EBF-BE40-454F-9871-9D60556AA663}.Release|x86.ActiveCfg = Release|Any CPU 32 | {16A64EBF-BE40-454F-9871-9D60556AA663}.Release|x86.Build.0 = Release|Any CPU 33 | {0DDD48DC-8D9E-4665-8A08-E91545D6194B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 | {0DDD48DC-8D9E-4665-8A08-E91545D6194B}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 | {0DDD48DC-8D9E-4665-8A08-E91545D6194B}.Debug|x64.ActiveCfg = Debug|Any CPU 36 | {0DDD48DC-8D9E-4665-8A08-E91545D6194B}.Debug|x64.Build.0 = Debug|Any CPU 37 | {0DDD48DC-8D9E-4665-8A08-E91545D6194B}.Debug|x86.ActiveCfg = Debug|Any CPU 38 | {0DDD48DC-8D9E-4665-8A08-E91545D6194B}.Debug|x86.Build.0 = Debug|Any CPU 39 | {0DDD48DC-8D9E-4665-8A08-E91545D6194B}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {0DDD48DC-8D9E-4665-8A08-E91545D6194B}.Release|Any CPU.Build.0 = Release|Any CPU 41 | {0DDD48DC-8D9E-4665-8A08-E91545D6194B}.Release|x64.ActiveCfg = Release|Any CPU 42 | {0DDD48DC-8D9E-4665-8A08-E91545D6194B}.Release|x64.Build.0 = Release|Any CPU 43 | {0DDD48DC-8D9E-4665-8A08-E91545D6194B}.Release|x86.ActiveCfg = Release|Any CPU 44 | {0DDD48DC-8D9E-4665-8A08-E91545D6194B}.Release|x86.Build.0 = Release|Any CPU 45 | {53B90B0B-61C5-4F25-BF2D-B51B8903D37C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 46 | {53B90B0B-61C5-4F25-BF2D-B51B8903D37C}.Debug|Any CPU.Build.0 = Debug|Any CPU 47 | {53B90B0B-61C5-4F25-BF2D-B51B8903D37C}.Debug|x64.ActiveCfg = Debug|Any CPU 48 | {53B90B0B-61C5-4F25-BF2D-B51B8903D37C}.Debug|x64.Build.0 = Debug|Any CPU 49 | {53B90B0B-61C5-4F25-BF2D-B51B8903D37C}.Debug|x86.ActiveCfg = Debug|Any CPU 50 | {53B90B0B-61C5-4F25-BF2D-B51B8903D37C}.Debug|x86.Build.0 = Debug|Any CPU 51 | {53B90B0B-61C5-4F25-BF2D-B51B8903D37C}.Release|Any CPU.ActiveCfg = Release|Any CPU 52 | {53B90B0B-61C5-4F25-BF2D-B51B8903D37C}.Release|Any CPU.Build.0 = Release|Any CPU 53 | {53B90B0B-61C5-4F25-BF2D-B51B8903D37C}.Release|x64.ActiveCfg = Release|Any CPU 54 | {53B90B0B-61C5-4F25-BF2D-B51B8903D37C}.Release|x64.Build.0 = Release|Any CPU 55 | {53B90B0B-61C5-4F25-BF2D-B51B8903D37C}.Release|x86.ActiveCfg = Release|Any CPU 56 | {53B90B0B-61C5-4F25-BF2D-B51B8903D37C}.Release|x86.Build.0 = Release|Any CPU 57 | EndGlobalSection 58 | GlobalSection(SolutionProperties) = preSolution 59 | HideSolutionNode = FALSE 60 | EndGlobalSection 61 | GlobalSection(ExtensibilityGlobals) = postSolution 62 | SolutionGuid = {48CB1B36-7AA4-4FCA-83BC-88D203EE812C} 63 | EndGlobalSection 64 | EndGlobal 65 | -------------------------------------------------------------------------------- /samples/RouterDemo/App.cshtml: -------------------------------------------------------------------------------- 1 |  5 | 6 | -------------------------------------------------------------------------------- /samples/RouterDemo/Pages/HelloPlanet.cshtml: -------------------------------------------------------------------------------- 1 | @page "/hello-planet/{Planet}" 2 | 3 |

Hello @Planet!

4 | 5 | 6 | 7 | @functions { 8 | [Parameter] 9 | private string Planet { get; set; } 10 | 11 | protected override void OnInit() 12 | { 13 | Console.WriteLine(Planet); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /samples/RouterDemo/Pages/HelloUniverse.cshtml: -------------------------------------------------------------------------------- 1 | @page "/hello-universe" 2 | 3 |

Hello Universe!

4 | 5 | 6 | -------------------------------------------------------------------------------- /samples/RouterDemo/Pages/HelloWorld.cshtml: -------------------------------------------------------------------------------- 1 | @page "/hello-world" 2 | @implements IDisposable 3 | @inject Microsoft.AspNetCore.Blazor.Services.IUriHelper UriHelper 4 | 5 |

Hello @Type World!

6 | 7 | 8 | 9 | @functions { 10 | private string Type { get; set; } 11 | 12 | protected override void OnInit() 13 | { 14 | RefreshType(); 15 | UriHelper.OnLocationChanged += OnLocationChanges; 16 | } 17 | 18 | private void OnLocationChanges(object sender, string location) => RefreshType(); 19 | 20 | private void RefreshType() 21 | { 22 | var uri = new Uri(UriHelper.GetAbsoluteUri()); 23 | Type = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(uri.Query).TryGetValue("type", out var type) ? type.First() : ""; 24 | StateHasChanged(); 25 | } 26 | 27 | public void Dispose() 28 | { 29 | UriHelper.OnLocationChanged -= OnLocationChanges; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /samples/RouterDemo/Pages/MainMenu.cshtml: -------------------------------------------------------------------------------- 1 | @page "/" 2 |

3 | Main Menu
4 | Hello Universe
5 | Hello World
6 | Hello beautiful World
7 | Hello Moon
8 | Hello Planet Epsilon Eridani
9 | Navigate in Code 10 |

11 | 12 |

13 | Main Menu
14 | Hello Universe
15 |

16 | -------------------------------------------------------------------------------- /samples/RouterDemo/Pages/NavigateInCode.cshtml: -------------------------------------------------------------------------------- 1 | @page "/navigate-in-code" 2 | @inject Microsoft.AspNetCore.Blazor.Services.IUriHelper UriHelper 3 | 4 | 5 | 6 | 7 | 8 | @functions { 9 | private void Navigate() 10 | { 11 | UriHelper.NavigateTo("/hello-world"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /samples/RouterDemo/Pages/OuterSpace/HelloMoon.cshtml: -------------------------------------------------------------------------------- 1 | @page "/outer-space/hello-moon" 2 | 3 |

Hello Moon!

4 | 5 | 6 | -------------------------------------------------------------------------------- /samples/RouterDemo/Pages/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @layout MainLayout 2 | -------------------------------------------------------------------------------- /samples/RouterDemo/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Blazor.Hosting; 2 | 3 | namespace RouterDemo 4 | { 5 | public class Program 6 | { 7 | public static void Main(string[] args) 8 | { 9 | CreateHostBuilder(args).Build().Run(); 10 | } 11 | 12 | public static IWebAssemblyHostBuilder CreateHostBuilder(string[] args) => 13 | BlazorWebAssemblyHost.CreateDefaultBuilder() 14 | .UseBlazorStartup(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/RouterDemo/RouterDemo.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | dotnet 6 | blazor serve 7 | 7.3 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /samples/RouterDemo/RouterDemo.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27428.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RouterDemo", "RouterDemo.csproj", "{F3381F4D-B5C8-4FF1-9D8B-CF07F55DF9EA}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {F3381F4D-B5C8-4FF1-9D8B-CF07F55DF9EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {F3381F4D-B5C8-4FF1-9D8B-CF07F55DF9EA}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {F3381F4D-B5C8-4FF1-9D8B-CF07F55DF9EA}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {F3381F4D-B5C8-4FF1-9D8B-CF07F55DF9EA}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {3234EBA9-E587-446E-A34F-41DDE1D87364} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /samples/RouterDemo/Shared/MainLayout.cshtml: -------------------------------------------------------------------------------- 1 | @inherits BlazorLayoutComponent 2 | 3 | @Body 4 | -------------------------------------------------------------------------------- /samples/RouterDemo/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Blazor.Builder; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace RouterDemo 9 | { 10 | public class Startup 11 | { 12 | public void ConfigureServices(IServiceCollection services) 13 | { 14 | } 15 | 16 | public void Configure(IBlazorApplicationBuilder app) 17 | { 18 | app.AddComponent("app"); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /samples/RouterDemo/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using Microsoft.AspNetCore.Blazor 3 | @using Microsoft.AspNetCore.Blazor.Components 4 | @using Microsoft.AspNetCore.Blazor.Layouts 5 | @using Microsoft.AspNetCore.Blazor.Routing 6 | @using RouterDemo 7 | @using RouterDemo.Shared 8 | -------------------------------------------------------------------------------- /samples/RouterDemo/wwwroot/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | learn-blazor.com || Blazor Pages 6 | 7 | 8 | 9 | Loading... 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /samples/StaticHosting/App.cshtml: -------------------------------------------------------------------------------- 1 |  5 | 6 | -------------------------------------------------------------------------------- /samples/StaticHosting/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:alpine 2 | COPY ./bin/Debug/netstandard2.0/dist /usr/share/nginx/html/ 3 | COPY nginx.conf /etc/nginx/nginx.conf 4 | -------------------------------------------------------------------------------- /samples/StaticHosting/Pages/Page1.cshtml: -------------------------------------------------------------------------------- 1 | @page "/" 2 | @page "/Page1" 3 |

Page1

4 | -------------------------------------------------------------------------------- /samples/StaticHosting/Pages/Page2.cshtml: -------------------------------------------------------------------------------- 1 | @page "/Page2" 2 |

Page2

3 | -------------------------------------------------------------------------------- /samples/StaticHosting/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Blazor.Browser.Rendering; 2 | using Microsoft.AspNetCore.Blazor.Browser.Services; 3 | using System; 4 | 5 | namespace StaticHosting 6 | { 7 | class Program 8 | { 9 | static void Main(string[] args) 10 | { 11 | var serviceProvider = new BrowserServiceProvider(configure => { }); 12 | 13 | new BrowserRenderer(serviceProvider).AddComponent("app"); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/StaticHosting/StaticHosting.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | dotnet 6 | blazor serve 7 | 7.3 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /samples/StaticHosting/StaticHosting.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27512.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StaticHosting", "StaticHosting.csproj", "{032AEBE7-1EB8-4EA7-971D-37E56F698618}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {032AEBE7-1EB8-4EA7-971D-37E56F698618}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {032AEBE7-1EB8-4EA7-971D-37E56F698618}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {032AEBE7-1EB8-4EA7-971D-37E56F698618}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {032AEBE7-1EB8-4EA7-971D-37E56F698618}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {022209DB-2650-4916-ACE3-6C9A1412779B} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /samples/StaticHosting/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using Microsoft.AspNetCore.Blazor 3 | @using Microsoft.AspNetCore.Blazor.Components 4 | @using Microsoft.AspNetCore.Blazor.Layouts 5 | @using Microsoft.AspNetCore.Blazor.Routing 6 | @using StaticHosting 7 | -------------------------------------------------------------------------------- /samples/StaticHosting/nginx.conf: -------------------------------------------------------------------------------- 1 | events { } 2 | http { 3 | server { 4 | listen 80; 5 | 6 | location / { 7 | root /usr/share/nginx/html; 8 | try_files $uri $uri/ /index.html =404; 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /samples/StaticHosting/wwwroot/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | StaticHosting 6 | 7 | 8 | 9 | Loading... 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /samples/ViewLogicSeparation/ViewLogicSeparation.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27512.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ViewLogicSeparation", "ViewLogicSeparation\ViewLogicSeparation.csproj", "{79A838C8-9EE1-40A4-AF40-36FAFBE4570C}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {79A838C8-9EE1-40A4-AF40-36FAFBE4570C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {79A838C8-9EE1-40A4-AF40-36FAFBE4570C}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {79A838C8-9EE1-40A4-AF40-36FAFBE4570C}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {79A838C8-9EE1-40A4-AF40-36FAFBE4570C}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {FFC320D6-2ED0-439E-8C18-6312E54609DD} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /samples/ViewLogicSeparation/ViewLogicSeparation/App.cshtml: -------------------------------------------------------------------------------- 1 |  5 | 6 | -------------------------------------------------------------------------------- /samples/ViewLogicSeparation/ViewLogicSeparation/Logic/DataAccess.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Blazor.Components; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Net.Http; 5 | using System.Threading.Tasks; 6 | 7 | namespace ViewLogicSeparation.Logic 8 | { 9 | public interface IDataAccess 10 | { 11 | Task> GetCustomersAsync(string customerFilter); 12 | } 13 | 14 | public class DataAccess : IDataAccess 15 | { 16 | private HttpClient Http { get; set; } 17 | 18 | public DataAccess(HttpClient http) 19 | { 20 | // We use constructor injection here because the `InjectAttribute` only works 21 | // on Blazor components, not recursively on injected services. 22 | Http = http; 23 | } 24 | 25 | public async Task> GetCustomersAsync(string customerFilter) 26 | { 27 | #region Check prerequisites 28 | if (Http == null) 29 | { 30 | // This should never happen. This check should demonstrate that 31 | // `Http` is filled by Blazor's dependency injection mechanism. 32 | throw new InvalidOperationException("There is something wrong with DI"); 33 | } 34 | 35 | if (customerFilter == null) 36 | { 37 | // No customer filter was given. 38 | throw new ArgumentNullException(nameof(customerFilter)); 39 | } 40 | #endregion 41 | 42 | // Simulate async data access. In practice, this would be 43 | // e.g. a call to a web api using `Http`. The parameter 44 | // `customerFilter` would be used in the call. 45 | await Task.Delay(300); 46 | 47 | // Simulate a returned result 48 | return new[] 49 | { 50 | new Customer { FirstName = "Bruce", LastName = "Wayne" }, 51 | new Customer { FirstName = "Clark", LastName = "Kent" } 52 | }; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /samples/ViewLogicSeparation/ViewLogicSeparation/Logic/Model.cs: -------------------------------------------------------------------------------- 1 | namespace ViewLogicSeparation.Logic 2 | { 3 | public class Customer 4 | { 5 | public string FirstName { get; set; } 6 | public string LastName { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /samples/ViewLogicSeparation/ViewLogicSeparation/Pages/Inheritance.cshtml: -------------------------------------------------------------------------------- 1 | @page "/" 2 | @inherits InheritanceBase 3 | 4 |

Inheritance

5 | 6 |
7 | 8 |
10 | 11 | @if (CanGetCustomers) 12 | { 13 | 14 | } 15 |
16 | 17 | 18 | @if (CustomersLoaded) 19 | { 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | @foreach (var customer in Customers) 30 | { 31 | 32 | 33 | 34 | 35 | } 36 | 37 |
First NameLast Name
@customer.FirstName@customer.FirstName
38 |
39 | } 40 | 41 | 42 | -------------------------------------------------------------------------------- /samples/ViewLogicSeparation/ViewLogicSeparation/Pages/InheritanceBase.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Blazor.Components; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Net.Http; 5 | using ViewLogicSeparation.Logic; 6 | 7 | namespace ViewLogicSeparation.Pages 8 | { 9 | public class InheritanceBase : BlazorComponent 10 | { 11 | #region Injected properties 12 | // Note that Blazor's dependency injection works even if you use the 13 | // `InjectAttribute` in a component's base class. 14 | 15 | // Note that we decouple the component's base class from 16 | // the data access service using an interface. 17 | [Inject] 18 | protected IDataAccess DataAccess { get; set; } 19 | #endregion 20 | 21 | #region Properties used for data binding 22 | public IEnumerable Customers { get; set; } 23 | 24 | public string CustomerFilter { get; set; } 25 | #endregion 26 | 27 | #region Status properties used to enable/disable/hide/show UI elements 28 | public bool CustomersLoaded => Customers != null; 29 | 30 | public bool IsInitialized => DataAccess != null; 31 | 32 | public bool CanGetCustomers => IsInitialized && !string.IsNullOrEmpty(CustomerFilter); 33 | #endregion 34 | 35 | // At the time of writing, `@onclickasync` does not exist. Therefore, 36 | // the return type has to be `void` instead of `Task`. 37 | public async void GetCustomers() 38 | { 39 | #region Check prerequisites 40 | if (DataAccess == null) 41 | { 42 | // This should never happen. This check should demonstrate that 43 | // `DataAccess` and `Http` are filled by Blazor's dependency injection. 44 | throw new InvalidOperationException("There is something wrong with DI"); 45 | } 46 | 47 | if (!CanGetCustomers) 48 | { 49 | // This should never happen. The UI should prevent calling 50 | // `GetCustomerAsync` if no customer filter has been set (e.g. by 51 | // disabling or hiding the button). 52 | throw new InvalidOperationException("Customer filter not set"); 53 | } 54 | #endregion 55 | 56 | // Get the data using the injected data access service 57 | Customers = await DataAccess.GetCustomersAsync(CustomerFilter); 58 | 59 | // We have to manually call `StateHasChanged` because Blazor's `onclick` 60 | // does not yet support async handler methods. 61 | StateHasChanged(); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /samples/ViewLogicSeparation/ViewLogicSeparation/Pages/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @layout MainLayout 2 | -------------------------------------------------------------------------------- /samples/ViewLogicSeparation/ViewLogicSeparation/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Blazor.Browser.Rendering; 2 | using Microsoft.AspNetCore.Blazor.Browser.Services; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using System; 5 | using ViewLogicSeparation.Logic; 6 | 7 | namespace ViewLogicSeparation 8 | { 9 | class Program 10 | { 11 | static void Main(string[] args) 12 | { 13 | var serviceProvider = new BrowserServiceProvider(configure => 14 | { 15 | configure.Add(ServiceDescriptor.Singleton()); 16 | }); 17 | 18 | new BrowserRenderer(serviceProvider).AddComponent("app"); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /samples/ViewLogicSeparation/ViewLogicSeparation/Shared/MainLayout.cshtml: -------------------------------------------------------------------------------- 1 | @inherits BlazorLayoutComponent 2 | 3 | @Body 4 | -------------------------------------------------------------------------------- /samples/ViewLogicSeparation/ViewLogicSeparation/ViewLogicSeparation.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | dotnet 6 | blazor serve 7 | 7.3 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /samples/ViewLogicSeparation/ViewLogicSeparation/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using Microsoft.AspNetCore.Blazor 3 | @using Microsoft.AspNetCore.Blazor.Components 4 | @using Microsoft.AspNetCore.Blazor.Layouts 5 | @using Microsoft.AspNetCore.Blazor.Routing 6 | @using ViewLogicSeparation 7 | @using ViewLogicSeparation.Shared 8 | -------------------------------------------------------------------------------- /samples/ViewLogicSeparation/ViewLogicSeparation/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 15px; 3 | max-width: 50%; 4 | font-family: 'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif 5 | } 6 | 7 | .result { 8 | margin-top: 30px; 9 | } 10 | -------------------------------------------------------------------------------- /samples/ViewLogicSeparation/ViewLogicSeparation/wwwroot/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | ViewLogicSeparation 6 | 7 | 8 | 9 | 10 | Loading... 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /static/images/architecture/click-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-architects/learn-blazor/7f6147a7afa24aa2aa0b1da85cd1c2a2a721d7ff/static/images/architecture/click-flow.png -------------------------------------------------------------------------------- /static/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-architects/learn-blazor/7f6147a7afa24aa2aa0b1da85cd1c2a2a721d7ff/static/images/favicon.png -------------------------------------------------------------------------------- /static/images/getting-started/blazor-architecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-architects/learn-blazor/7f6147a7afa24aa2aa0b1da85cd1c2a2a721d7ff/static/images/getting-started/blazor-architecture.jpg -------------------------------------------------------------------------------- /static/images/getting-started/chrome-load-dlls-061.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-architects/learn-blazor/7f6147a7afa24aa2aa0b1da85cd1c2a2a721d7ff/static/images/getting-started/chrome-load-dlls-061.png -------------------------------------------------------------------------------- /static/images/getting-started/chrome-load-dlls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-architects/learn-blazor/7f6147a7afa24aa2aa0b1da85cd1c2a2a721d7ff/static/images/getting-started/chrome-load-dlls.png -------------------------------------------------------------------------------- /static/images/getting-started/vs-project-template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-architects/learn-blazor/7f6147a7afa24aa2aa0b1da85cd1c2a2a721d7ff/static/images/getting-started/vs-project-template.png -------------------------------------------------------------------------------- /static/images/getting-started/vs-razor-compilation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-architects/learn-blazor/7f6147a7afa24aa2aa0b1da85cd1c2a2a721d7ff/static/images/getting-started/vs-razor-compilation.png -------------------------------------------------------------------------------- /static/images/getting-started/webserver-for-chrome-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-architects/learn-blazor/7f6147a7afa24aa2aa0b1da85cd1c2a2a721d7ff/static/images/getting-started/webserver-for-chrome-settings.png -------------------------------------------------------------------------------- /static/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 63 | 67 | 71 | 75 | 79 | 83 | 87 | 91 | 95 | 99 | 103 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /static/images/pages/demo-lifecycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-architects/learn-blazor/7f6147a7afa24aa2aa0b1da85cd1c2a2a721d7ff/static/images/pages/demo-lifecycle.png -------------------------------------------------------------------------------- /static/slides/Blazor-Intro.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-architects/learn-blazor/7f6147a7afa24aa2aa0b1da85cd1c2a2a721d7ff/static/slides/Blazor-Intro.pdf -------------------------------------------------------------------------------- /static/slides/Blazor-Intro.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-architects/learn-blazor/7f6147a7afa24aa2aa0b1da85cd1c2a2a721d7ff/static/slides/Blazor-Intro.pptx --------------------------------------------------------------------------------