This is a rough guide of what topics are best to introduce with each section.
@code
- this is like the old @functions
feature from .cshtml
. Get users comfortable with the idea of defining properties, fields, methods, even nested classesGetFromJsonAsync
)OnInitializedAsync
and the common pattern of starting async work_Imports.razor
is the most common way to hook it updemos: All of the above can just be introduced with the template
EventCallback
as the fixvalue
and @onchange
)@bind
as a shorthand for the above@bind-value
+ @bind-value:event
as a more speciic version@bind
to do the default thing for common input types, but it's possible to specify what you want to binddemos: TodoList, with multiple levels of components
@bind-value:event="oninput"
TodoListEditor
component that takes readonly Text
and IsDone
parameters
IsDoneChanged
parameter and invoke a callback that manually updates model and calls StateHasChanged
@bind-IsDone
(change param type to EventCallback<bool>
).NavLink
in more detail, why would you use NavLink
instead of an <a>
OnInitializedAsync
and OnParametersSetAsync
StateHasChanged
with the context about background processing@implements
- implementing an interfaceDispose
as the counterpart to OnInitialized
NavigationManager
and programmatic navigationdemos: a counter with a timer
NavMenu.razor
, replace all <NavLink>
with <a>
and see how it still works, except no highlighting
<NavLink>
and see it still renders <a>
tags except with "active" classNavLinkMatch
/
because of <base href>
Counter.razor
to take an initial startCount
param
:int
route constraint.Counter
, if the count exceeds 5, auto-navigate to Index
Counter.razor
, make OnInitialized
start up a timer that increments count and logs to console
IDisposable
EventCallback
)demos before
CounterState
class and make it into a singleton DI service
demos after
@inject CounterState
into MainLayout.razor
and add <button @onclick="() => { counterState.Count++; }">Increment</button>
Counter.razor
, because nothing tells the framework that your actions against a DI service in one component may affect another componentMainLayout.razor
, add a @code
block declaring a field with value new CounterState()
@Body
with <CascadingValue Value=@counterState>
- see it workMainLayout
causes an update to Counter
nowEditForm
and input componentsdemos-before: todolist with validation
TodoItem
class-------
or 15+3
, but when adding the item it got reset to 0, which doesn't make sense as UX<form>
to <EditForm>
and explain its responsibilities
Model="newItem"
where newItem
is the a TodoItem
fieldOnSubmit
, OnValidSubmit
, OnInvalidSubmit
OnValidSubmit
to your submission method<DataAnnotationsValidator />
to the form
<ValidationSummary />
and see it displays reasons<input>
=> <InputText>
etc
<ValidationSummary>
with <ValidationMessage>
for each fielddemos-after: tri-state checkbox OR slider component
@bind
with the HTML.@attributes
on the output if it makes sense to add aribtrary user-supplied attributes to a particular element. Explain "last wins" rule.demos
Microsoft.AspNetCore.Components.WebAssembly.Authentication
_Imports.razor
, added Microsoft.AspNetCore.Authorization
and Microsoft.AspNetCore.Components.Authorization
First you want to show the name of the logged in user. In MainLayout.razor
, inside the top bar, add:
<AuthorizeView> <Authorized> <strong>Hello, @context.User.Identity.Name!</strong> </Authorized> <NotAuthorized> You're not logged in. Please log in! </NotAuthorized> </AuthorizeView>
At first this gives an exception (no cascading auth state). Fix by adding <CascadingAuthenticationState>
around everything in App.razor
AuthenticationStateProvider
.
class MyFakeAuthenticationStateProvider : AuthenticationStateProvider { public override Task<AuthenticationState> GetAuthenticationStateAsync() { // Hard-coded logged-out user var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity()); return Task.FromResult(new AuthenticationState(claimsPrincipal)); } }
Counter.razor
to logged in users
[Authorize]
there - see it has no effectAuthorizeRouteView
- see it works nowNotAuthorized
template<AuthorizeView>
MyFakeAuthenticationStateProvider
to have a hardcoded logged-in user:var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, "Bert"), //new Claim(ClaimTypes.Role, "superadmin") }, "fake"));
[Authorize]
and <AuthorizeView>
. But what if you want to use auth state programmatically during logic?superadmin
<AuthorizeView Roles="superadmin"> <Authorized> <p>You're a superadmin, so you can count as high as you like</p> </Authorized> <NotAuthorized> <p>You're a loser, so you can only count up to 3</p> </NotAuthorized> </AuthorizeView>
[CascadingParameter] public Task<AuthenticationState> AuthStateTask { get; set; }
and then inside the count logic, await
it to get authState
and then check if (currentCount < 3 || authState.User.IsInRole("superadmin"))
dotnet new blazorwasm --hosted --auth Individual -o MyRealAuthApp
Program.cs
how this is configuredIJSRuntime
with a simple example (like showing a JavaScript alert
)OnAfterRender
and in response to eventsIJSRuntime
is async for everything, why that's important, what to do if you need low-level synchronous interopDotNetObjectRef<>
, the actual usage of JS interop in the workshop is pretty simpledemos-before: alert() demos-after: DotNetObjectRef
RenderFragment
, talk about how its used to pass markup and rendering logic to other components, recall examples like AuthorizeView
and the MainLayout
ChildContent
propertyRenderFragment
parametersRenderFragment<>
that requires an argument @typeparam
and compare to a generic class written in C#demo: material design components
First, we need to review how event dispatching interacts with rendering. Components will automatically re-render (update the DOM) when their parameters have changed, or when they receive an event (like @onclick
). This generally works for the most common cases. This also makes sense because it would be infeasible to rerender the entire UI each time an event happens - Blazor has to make a decision about what part of the UI should update.
An event handler is attached to a .NET Delegate
and the component that receives the event notification is defined by Delegate.Target
. Roughly-defined, if the delegate represents an instance method of some object, then the Target
will be the object instance whose method is being invoked.
In the following example the event handler delegate is TestComponent.Clicked
and the Delegate.Target
is the instance of TestComponent
.
@* TestComponent.razor *@ <button @onclick="Clicked">Click me!</Clicked> <p>Clicked @i times!</p> @code { int i; void Clicked() { i++; } }
Since usually the purpose of an event handler is to call a method that updates the private state of a component, it makes sense that Blazor would want to rerender the component that defines the event handler. The previous example is the most simple use of an event handler, and it makes sense that we'd want to rerender TestComponent
in this case.
Now let's consider what happens when we want an event to rerender an ancestor component. This is similar to what happens with Index
and ConfigurePizzaDialog
- and it just works for the typical case where the event handler is a parameter. This example will use Action
instead of EventCallback
since we're building up to an explanation of why EventCallback
is needed.
@* CoolButton.razor *@ <button @onclick="Clicked">Clicking this will be cool!</button> @code { [Parameter] public Action Clicked { get; set; } } @* TestComponent2.razor *@ <CoolButton Clicked="Clicked" /> <p>Clicked @i times!</p> @code { int i; void Clicked() { i++; } }
In the this example the event handler delegate is TestComponent2.Clicked
and the Delegate.Target
is the instance of TestComponent
- even though it's CoolButton
that actually defines the event. This means that TestComponent2
will be rerendered when the button is clicked. This makes sense because if TestComponent2
didn't get rerendered, you couldn't update the count.
Let's see a third example to show how this falls apart. There are cases where Delegate.Target
isn't a component at all, and so nothing will rerender. Let's see that example again, but with an AppState object:
public class TestState { public int Count { get; private set; } public void Clicked() { Count++; } }
@* CoolButton.razor *@ <button @onclick="Clicked">Clicking this will be cool!</button> @code { [Parameter] public Action Clicked { get; set; } } @* TestComponent3.razor *@ @inject TestState State <CoolButton Clicked="@State.Clicked" /> <p>Clicked @State.Count times!</p>
In this third example the event handler delegate is TestState.Clicked
and the so Delegate.Target
is TestState
- not a component. When the button is clicked, no component gets the event notification, and so nothing will rerender.
This is the problem that EventCallback
was created to solve. By changing the parameter on CoolButton
from Action
to EventCallback
you fix the event dispatching behavior. This works because EventCallback
is known to the compiler, when you create an EventCallback
from a delegate that doesn't have its Target
set to a component, then the compiler will pass the current component to receive the event.
Let's jump back to our application. If you like you can reproduce the problem that's been described here by changing the parameters of ConfigurePizzaDialog
from EventCallback
to Action
. If you try this you can see that cancelling or confirming the dialog does nothing. This is because our use case is exactly like the third example above:
@* from Index.razor *@ @if (OrderState.ShowingConfigureDialog) { <ConfigurePizzaDialog Pizza="OrderState.ConfiguringPizza" OnConfirm="OrderState.ConfirmConfigurePizzaDialog" OnCancel="OrderState.CancelConfigurePizzaDialog" /> }
For the OnConfirm
and OnCancel
parameters, the Delegate.Target
will be OrderState
since we're passing reference to methods defined by OrderState
. If you're using EventCallback
then the special logic of the compiler kicks in and it will specify additional information to dispatch the event to Index
.