mirror of
https://github.com/m-lamonaca/dev-notes.git
synced 2025-05-15 15:44:47 +00:00
feat: restructure docs into "chapters" (#12)
* feat(docker, k8s): create containers folder and kubernetes notes
This commit is contained in:
parent
b1cb858508
commit
2725e3cb70
92 changed files with 777 additions and 367 deletions
435
docs/languages/dotnet/asp.net/blazor.md
Normal file
435
docs/languages/dotnet/asp.net/blazor.md
Normal file
|
@ -0,0 +1,435 @@
|
|||
# Blazor
|
||||
|
||||
Blazor apps are based on *components*. A **component** in Blazor is an element of UI, such as a page, dialog, or data entry form.
|
||||
|
||||
Components are .NET C# classes built into .NET assemblies that:
|
||||
|
||||
- Define flexible UI rendering logic.
|
||||
- Handle user events.
|
||||
- Can be nested and reused.
|
||||
- Can be shared and distributed as Razor class libraries or NuGet packages.
|
||||
|
||||

|
||||

|
||||
|
||||
The component class is usually written in the form of a Razor markup page with a `.razor` file extension. Components in Blazor are formally referred to as *Razor components*.
|
||||
|
||||
## Project Structure & Important Files
|
||||
|
||||
### Blazor Server Project Structure
|
||||
|
||||
```txt
|
||||
Project
|
||||
|-Properties
|
||||
| |- launchSettings.json
|
||||
|
|
||||
|-wwwroot --> static files
|
||||
| |-css
|
||||
| | |- site.css
|
||||
| | |- bootstrap
|
||||
| |
|
||||
| |- favicon.ico
|
||||
|
|
||||
|-Pages
|
||||
| |- _Host.cshtml --> fallback page
|
||||
| |- Component.razor
|
||||
| |- Index.razor
|
||||
| |- ...
|
||||
|
|
||||
|-Shared
|
||||
| |- MainLayout.razor
|
||||
| |- MainLayout.razor.css
|
||||
| |- ...
|
||||
|
|
||||
|- _Imports.razor --> @using imports
|
||||
|- App.razor --> component root of the app
|
||||
|
|
||||
|- appsettings.json --> application settings
|
||||
|- Program.cs --> App entry-point
|
||||
|- Startup.cs --> services and middleware configs
|
||||
```
|
||||
|
||||
### Blazor WASM Project Structure
|
||||
|
||||
```txt
|
||||
Project
|
||||
|-Properties
|
||||
| |- launchSettings.json
|
||||
|
|
||||
|-wwwroot --> static files
|
||||
| |-css
|
||||
| | |- site.css
|
||||
| | |- bootstrap
|
||||
| |
|
||||
| |- index.html
|
||||
| |- favicon.ico
|
||||
|
|
||||
|-Pages
|
||||
| |- Component.razor
|
||||
| |- Index.razor
|
||||
| |- ...
|
||||
|
|
||||
|-Shared
|
||||
| |- MainLayout.razor
|
||||
| |- MainLayout.razor.css
|
||||
| |- ...
|
||||
|
|
||||
|- _Imports.razor --> @using imports
|
||||
|- App.razor --> component root of the app
|
||||
|
|
||||
|- appsettings.json --> application settings
|
||||
|- Program.cs --> App entry-point
|
||||
```
|
||||
|
||||
### Blazor PWA Project Structure
|
||||
|
||||
```txt
|
||||
Project
|
||||
|-Properties
|
||||
| |- launchSettings.json
|
||||
|
|
||||
|-wwwroot --> static files
|
||||
| |-css
|
||||
| | |- site.css
|
||||
| | |- bootstrap
|
||||
| |
|
||||
| |- index.html
|
||||
| |- favicon.ico
|
||||
| |- manifest.json
|
||||
| |- service-worker.js
|
||||
| |- icon-512.png
|
||||
|
|
||||
|-Pages
|
||||
| |- Component.razor
|
||||
| |- Index.razor
|
||||
| |- ...
|
||||
|
|
||||
|-Shared
|
||||
| |- MainLayout.razor
|
||||
| |- MainLayout.razor.css
|
||||
| |- ...
|
||||
|
|
||||
|- _Imports.razor --> @using imports
|
||||
|- App.razor --> component root of the app
|
||||
|
|
||||
|- appsettings.json --> application settings
|
||||
|- Program.cs --> App entrypoint
|
||||
```
|
||||
|
||||
### `manifest.json`, `service-worker.js` (Blazor PWA)
|
||||
|
||||
[PWA](https://web.dev/progressive-web-apps/)
|
||||
[PWA MDN Docs](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps)
|
||||
[PWA Manifest](https://developer.mozilla.org/en-US/docs/Web/Manifest)
|
||||
[Service Worker API](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API)
|
||||
|
||||
```json
|
||||
// manifest.json
|
||||
{
|
||||
"name": "<App Name>",
|
||||
"short_name": "<Short App Name>",
|
||||
"start_url": "./",
|
||||
"display": "standalone",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#03173d",
|
||||
"icons": [
|
||||
{
|
||||
"src": "icon-512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Common Blazor Files
|
||||
|
||||
### `App.razor`
|
||||
|
||||
```cs
|
||||
<Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
|
||||
</Found>
|
||||
<NotFound>
|
||||
<LayoutView Layout="@typeof(MainLayout)">
|
||||
<p>Sorry, there's nothing at this address.</p>
|
||||
</LayoutView>
|
||||
</NotFound>
|
||||
</Router>
|
||||
```
|
||||
|
||||
### `MainLayout.razor` (Blazor Server/WASM)
|
||||
|
||||
```cs
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
<div class="page">
|
||||
<div class="sidebar">
|
||||
<NavMenu /> // NavMenu Component
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="top-row px-4">
|
||||
</div>
|
||||
|
||||
<div class="content px-4">
|
||||
@Body
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### `_Host.cshtml` (Blazor Server)
|
||||
|
||||
```html
|
||||
@page "/"
|
||||
@namespace BlazorServerDemo.Pages
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@{
|
||||
Layout = null;
|
||||
}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>BlazorServerDemo</title>
|
||||
<base href="~/" />
|
||||
<link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
|
||||
<link href="css/site.css" rel="stylesheet" />
|
||||
<link href="BlazorServerDemo.styles.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<component type="typeof(App)" render-mode="ServerPrerendered" />
|
||||
|
||||
<div id="blazor-error-ui">
|
||||
<environment include="Staging,Production">
|
||||
An error has occurred. This application may no longer respond until reloaded.
|
||||
</environment>
|
||||
<environment include="Development">
|
||||
An unhandled exception has occurred. See browser dev tools for details.
|
||||
</environment>
|
||||
<a href="" class="reload">Reload</a>
|
||||
<a class="dismiss">🗙</a>
|
||||
</div>
|
||||
|
||||
<script src="_framework/blazor.server.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Components (`.razor`)
|
||||
|
||||
[Blazor Components](https://docs.microsoft.com/en-us/aspnet/core/blazor/components/)
|
||||
|
||||
```cs
|
||||
@page "/route/{RouteParameter}" // make component accessible from a URL
|
||||
@page "/route/{RouteParameter?}" // specify route parameter as optional
|
||||
@page "/route/{RouteParameter:<type>}" // specify route parameter type
|
||||
|
||||
@namespace <Namespace> // set the component namespace
|
||||
@using <Namespace> // using statement
|
||||
@inherits BaseType // inheritance
|
||||
@attribute [Attribute] // apply an attribute
|
||||
@inject Type objectName // dependency injection
|
||||
|
||||
// html of the page here
|
||||
|
||||
<Namespace.ComponentFolder.Component /> // access component w/o @using
|
||||
<Component Property="value"/> // insert component into page, passing attributes
|
||||
<Component @onclick="@CallbackMethod">
|
||||
@ChildContent // segment of UI content
|
||||
</Component>
|
||||
|
||||
@code {
|
||||
// component model (Properties, Methods, ...)
|
||||
|
||||
[Parameter] // capture attribute
|
||||
public Type Property { get; set; } = defaultValue;
|
||||
|
||||
[Parameter] // capture route parameters
|
||||
public type RouteParameter { get; set;}
|
||||
|
||||
[Parameter] // segment of UI content
|
||||
public RenderFragment ChildContent { get; set;}
|
||||
|
||||
private void CallbackMethod() { }
|
||||
}
|
||||
```
|
||||
|
||||
## State Management
|
||||
|
||||
## Passing state with `NavigationManager`
|
||||
|
||||
It's now possible to pass state when navigating in Blazor apps using the `NavigationManager`.
|
||||
|
||||
```cs
|
||||
navigationManager.NavigateTo("/<route>", new NavigationOptions { HistoryEntryState = value });
|
||||
```
|
||||
|
||||
This mechanism allows for simple communication between different pages. The specified state is pushed onto the browser’s history stack so that it can be accessed later using either the `NavigationManager.HistoryEntryState` property or the `LocationChangedEventArgs.HistoryEntryState` property when listening for location changed events.
|
||||
|
||||
### Blazor WASM
|
||||
|
||||
```cs
|
||||
// setup state singleton
|
||||
builder.Services.AddSingleton<StateContainer>();
|
||||
```
|
||||
|
||||
```cs
|
||||
// StateContainer singleton
|
||||
using System;
|
||||
|
||||
public class StateContainer
|
||||
{
|
||||
private int _counter;
|
||||
|
||||
public string Property
|
||||
{
|
||||
get => _counter;
|
||||
set
|
||||
{
|
||||
_counter = value;
|
||||
NotifyStateChanged(); // will trigger StateHasChanged(), causing a render
|
||||
}
|
||||
}
|
||||
|
||||
public event Action OnChange;
|
||||
|
||||
private void NotifyStateChanged() => OnChange?.Invoke();
|
||||
}
|
||||
```
|
||||
|
||||
```cs
|
||||
// component that changes the state
|
||||
@inject StateContainer State
|
||||
|
||||
// Delegate event handlers automatically trigger a UI render
|
||||
<button @onClick="@HandleClick">
|
||||
Change State
|
||||
</button>
|
||||
|
||||
@code {
|
||||
private void HandleClick()
|
||||
{
|
||||
State.Property += 1; // update state
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```cs
|
||||
// component that should be update on state change
|
||||
@implements IDisposable
|
||||
@inject StateContainer State
|
||||
|
||||
<p>Property: <b>@State.Property</b></p>
|
||||
|
||||
@code {
|
||||
|
||||
// StateHasChanged notifies the component that its state has changed.
|
||||
// When applicable, calling StateHasChanged causes the component to be rerendered.
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
State.OnChange += StateHasChanged;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
State.OnChange -= StateHasChanged;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Data Binding & Events
|
||||
|
||||
```cs
|
||||
<p>
|
||||
<button @on{DOM EVENT}="{DELEGATE}" />
|
||||
<button @on{DOM EVENT}="{DELEGATE}" @on{DOM EVENT}:preventDefault /> // prevent default action
|
||||
<button @on{DOM EVENT}="{DELEGATE}" @on{DOM EVENT}:preventDefault="{CONDITION}" /> // prevent default action if CONDITION is true
|
||||
|
||||
<button @on{DOM EVENT}="{DELEGATE}" @on{DOM EVENT}:stopPropagation />
|
||||
<button @on{DOM EVENT}="{DELEGATE}" @on{DOM EVENT}:stopPropagation="{CONDITION}" /> // stop event propagation if CONDITION is true
|
||||
|
||||
<button @on{DOM EVENT}="@(e => Property = value)" /> // change internal state w/ lambda
|
||||
<button @on{DOM EVENT}="@(e => DelegateAsync(e, value))" /> // invoke delegate w/ lambda
|
||||
|
||||
<input @ref="elementReference" />
|
||||
|
||||
<input @bind="{PROPERTY}" /> // updates variable on ONCHANGE event (focus loss)
|
||||
<input @bind="{PROPERTY}" @bind:event="{DOM EVENT}" /> // updates value on DOM EVENT
|
||||
<input @bind="{PROPERTY}" @bind:format="{FORMAT STRING}" /> // use FORMAT STRING to display value
|
||||
|
||||
<ChildComponent @bind-{PROPERTY}="{PROPERTY}" @bind-{PROPERTY}:event="{EVENT}" /> // bind to child component {PROPERTY}
|
||||
<ChildComponent @bind-{PROPERTY}="{PROPERTY}" @bind-{PROPERTY}:event="{PROPERTY}Changed" /> // bind to child component {PROPERTY}, listen for custom event
|
||||
|
||||
<input @bind="{PROPERTY}" @bind:after="{DELEGATE}" /> // run async logic after bind event completion
|
||||
<input @bind:get="{PROPERTY}" @bind:set="PropertyChanged" /> // two-way data binding
|
||||
</p>
|
||||
|
||||
@code {
|
||||
private ElementReference elementReference;
|
||||
|
||||
public string Property { get; set; }
|
||||
|
||||
public EventCallback<Type> PropertyChanged { get; set; } // custom event {PROPERTY}Changed
|
||||
|
||||
// invoke custom event
|
||||
public async Task DelegateAsync(EventArgs e, Type argument)
|
||||
{
|
||||
/* ... */
|
||||
|
||||
await PropertyChanged.InvokeAsync(e, argument); // notify parent bound prop has changed
|
||||
await elementReference.FocusAsync(); // focus an element in code
|
||||
}
|
||||
|
||||
[Parameter] public TValue Value { get; set; }
|
||||
[Parameter] public EventCallback<TValue> ValueChanged { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
> **Note**: When a user provides an unparsable value to a data-bound element, the unparsable value is automatically reverted to its previous value when the bind event is triggered.
|
||||
|
||||
> **Note**: The `@bind:get` and `@bind:set` modifiers are always used together.
|
||||
> `The @bind:get` modifier specifies the value to bind to and the `@bind:set` modifier specifies a callback that is called when the value changes
|
||||
|
||||
## Javascript/.NET Interop
|
||||
|
||||
[Call Javascript from .NET](https://docs.microsoft.com/en-us/aspnet/core/blazor/call-javascript-from-dotnet)
|
||||
[Call .NET from Javascript](https://docs.microsoft.com/en-us/aspnet/core/blazor/call-dotnet-from-javascript)
|
||||
|
||||
### Render Blazor components from JavaScript [C# 10]
|
||||
|
||||
To render a Blazor component from JavaScript, first register it as a root component for JavaScript rendering and assign it an identifier:
|
||||
|
||||
```cs
|
||||
// Blazor Server
|
||||
builder.Services.AddServerSideBlazor(options =>
|
||||
{
|
||||
options.RootComponents.RegisterForJavaScript<Counter>(identifier: "counter");
|
||||
});
|
||||
|
||||
// Blazor WebAssembly
|
||||
builder.RootComponents.RegisterForJavaScript<Counter>(identifier: "counter");
|
||||
```
|
||||
|
||||
Load Blazor into the JavaScript app (`blazor.server.js` or `blazor.webassembly.js`) and then render the component from JavaScript into a container element using the registered identifier, passing component parameters as needed:
|
||||
|
||||
```js
|
||||
let containerElement = document.getElementById('my-counter');
|
||||
await Blazor.rootComponents.add(containerElement, 'counter', { incrementAmount: 10 });
|
||||
```
|
||||
|
||||
### Blazor custom elements [C# 10]
|
||||
|
||||
Experimental support is also now available for building custom elements with Blazor using the Microsoft.AspNetCore.Components.CustomElements NuGet package.
|
||||
Custom elements use standard HTML interfaces to implement custom HTML elements.
|
||||
|
||||
To create a custom element using Blazor, register a Blazor root component as custom elements like this:
|
||||
|
||||
```cs
|
||||
options.RootComponents.RegisterAsCustomElement<Counter>("my-counter");
|
||||
```
|
Loading…
Add table
Add a link
Reference in a new issue