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 |
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 | 
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 | 
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 | 
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 | 
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 |
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 |
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 |