An online markdown blog and knowledge repository.
Collection of takeaways and key infos while learning more about ASP.NET, ASP.NET Core, and Blazor in .NET 6 and 8.
There are 3 "flavors" (my words) of Blazor:
Deploy a new Blazor Server using dotnet: dotnet new blazorserver -o MyBlazorServerProject -f net6.0
. This creates a new Blazor Server Project named "MyBlazorServerProject" using the DotNET 6 SDK.
Provides a WebAssembly capabilities where components are executed in the client browser, rather than on the server. Provides cross-platform compatibility and enhanced performance by eliminating round-trip WRRCs.
The number of available .NET APIs is limited, however speed and responsiveness are very high.
Serves-up static or dynamic pages to clients, based on ASP.NET Core technologies, and utilizes SignalR for back-end communications with each Web Client connection (WebSockets). Back-end data interop is built-in, and web page interactions are handled locally and by the server depending on the type of action.
Useful for for embedded applications and apps running on the local network.
Some limitation on performance due to WRRC round-trip time requirements.
Define UI layout and functionality, similar to React and Vue, and integrates with .NET MAUI for managing multi-platform support such as Android, iOS, and Linux. Designed for native apps, desktop, and mobile.
Blazor utilizes Razor Components, which are specially crafed Razor pages, using Razor syntax to integrate C# with HTML.
Razor Components are Pascal-case files with an extension of .razor
:
@
character.@page
(URL Path) of the component, and where C# @code
is allowed within the HTML markup.@
as well, i.e. <h3>My @webSiteName</h3>
.Deploy a new Razor Component using 'dotnet' like so dotnet new razorcomponent -n ComponentName -o Pages
. This will create a new Razor Component named ComponentName.razor
in the folder Pages
of an existing Blazor Server Project.
A Razor Page can be thought of as a C# class that includes HTML rendering logic.
Blazor Components:
<HeadContent>
element.<style>
elements within the components HTML (CSS Isolation).To build Blazor Server use the same methods as with other projects (dotnet build
or 'F6' using Visual Studio).
To build and run Blazor Server and force it up restart when code is changed (ala nodemon
) use dotnet watch
in the Terminal or PowerShell console in the Blazor Server Project path.
Microsoft.AspNetCore.Mvc
namespace at the top of a Class definition.[Route(string)]
and [ApiController]
attributes to designate the class as an MVC Controller.[HttpGet]
, [HttpPost]
, [HttpPatch]
, [HttpDelete]
(etc) REST call definitions to identify each Controller Method as an HTTP/API endpoint.There are 3 ways to do this:
[Parameter]
attribute to identify the Component Parameter in the Child Component.<ChildComponent Param=Value />
syntax to assign a value to the Component Parameter in the Parent Component.// child Component
<h1>Hello @Name</h1>
@code {
[Parameter]
public string Name {get; set;}
...
}
// parent Component
<h1>The computer says <ChildComponent Name=@UserName /></h1>
@code {
public string UserName = "Anony Mouse";
...
}
See Razor Lifecycle Events for details about when parameters are set and how to manage that event.
CascadingParameters
.[CascadingParameter(Key=string)]
to define the Cascading Parameter in the Component Fragment.<CascadingValue Key=string Value=string>
and </CascadingValue>
to wrap descendent Component elements so they have access to the Cascading Parameter(s).Note: Objects can be passed using Cascading Parameters - it is not limited to value-type variables.
Define the properties to store in a new Class and register it as a scoped service:
Program.cs
as a scoped service: builder.Services.AddScoped<StateObject>();
.@inject StateObject stateObj
.When an HTML element value is changed, the web page must be refreshed to show it.
@bind
does the hard work for you.@bind
understands whether value
, checked
, content
or some other attribute is necessary to bind with, based on the element type it is attached to.Bindings work on events, too:
onchange
DOM event: This is default trigger.oninput
fires whenever any character is entered into a TextBox, for example.@bind-value
and @bind-value:event
directives.Use the @bind:format
directive to update formatting.
See Format Strings in Blazor Documentation for details on when @bind:format
can be used.
Note: It is still possible to use pure C# string formatting techniques to apply:
Use @page
directive and <NavLink>
component both impact routing.
@page
helps configure Routes.@page
directive string argument to use as a match argument when determining if a page should be rendered at navigation.Router is configured in App.razor
:
<Found Context="routeData">
and <NotFound>
components tell the Router what to do.<routeData>
details.<RouteView props...>
defines the RouteData and the DefaultLayout to use.<NotFound>
element content.The @page
directive:
@page "/RouteName"
@page
directive can be used to define multiple routes that lead to the same page.NavigationManager manages handling portions of a URI:
https://domain.ext/parentRoute/relPath?key=value
https://domain.ext
parentRoute/relPath
?key=value
NavigationManager Usage:
@inject
ed into component where values will be used.NavigationManager.NavigateTo(AbsoluteUri | RelativeUri)
will send the user to another component in a code-call. This could be applied to a button onclick
event, for example.The <NavLink>
Component:
link:active
style.<NavLink Match="matcher">
manages when the link is highlighted.NavLinkMatch.All
: Only highlighted when href matches entire current URL.NavLinkMatch.Prefix
: Only highlighted when href matches first part of the current URL. Use this to help the user understand which section of the website they are viewing.<a href...>
elements.Capture a specifid parameter by using @page "/BaseRelativePath/{parameter}
. This makes the parameter available to the component, like Component Parameters.
?
question mark to make a route parameter optional. Set a default value within an appropriate Blazor Lifecycle Event such as OnInitialized()
or OnParametersSet()
(depending on what behavior is required).@page "/BaseRelativePath/{parameter:int}
Use these to ensure the parameter Type is matched before the Blazor Router sends the parameter to the Component.
Various types are allowed in Route Constraints:
Use *
prefix to capture multiple parameters: @page"/BaseRelativePath/{*parameter}
A match of 3 parameters could be stored in a string [parameter]
on the page and displayed to the user, or processed as input.
Use Layout Componets to set reusable page fragments for:
Layout files:
.razor
extension.@code
razor syntax code blocks.LayoutComponentBase
.@Body
directive in the location where referencing components will render their content.Do not include an @page
directive in Layout files - they do not handle requests and do not have routes created for them.
The default Layout file in new Blazor Projects is Shared/MainLayout.razor
.
Using Layouts in Blazor Components:
@layout
directive with the name of the Layout to apply to this component.@body
directive to the targeted Layout for the Component to render into.Apply a Template to all Blazor Components by using _Imports.razor
. In this case, using @layout
is not necessary, and instead the rendering will apply to all Components in the same folder as _Imports.razor
, and all its sub-folders.
Apply a default layout to every Component in all folders by configuring the <RouteView>
element and the DefaultLayout
attribute in App.razor
.
@layout
directive that will override the settings from App.razor
.App.razor:
Overview:
Events:
@
is used to identify Blazor-handled Events and custom event handlers in Blazor.Event Args:
MouseEventArgs
, ..., all inherit from base EventArgs
.NET Class.Blazor Events, Handling:
mousemove
or other common events that don't change UI state, it might be better to handle the event client-side using JavaScript.:preventDefault
to the <input>
element attributes list. See example below.// code reference: MSLearn "Blazor Improve How Forms Work
<input value=@data @onkeypress="ProcessKeyPress" @onkeypress:preventDefault />`
Asynchronous Event Handling:
async
with an appropriate return like Task
or Task<T>
to free the UI thread when the await
operator is used.FocusAsync
method:
ElementReference
field to store the InputField reference.@ref
attribute in-line with the HTML to tie-in the event to the handler.ChangeFocus()
and HandleFocus()
to perform actions.// code reference: MSLearn "Blazor Improve How Forms Work"
<button @onclick="ChangeFocus">Click to change focus</button>
<input @ref=InputField @onfocus="HandleFocus" value="@data" />
@code {
private ElementReference InputField;
private string data;
private async Task ChangeFocus()
{
await InputField.FocusAsync();
}
private async Task HandleFocus()
{
data = "Received focus";
}
}
Note: This example is not best practice. Forcing focus to a specific element is best utilize when there is an error on an input, especially within a Form, and the user needs to change the incorrect input before continuing. Avoid using this code to force users' navigation through a page.
Inline Event Handlers:
Event Default Behaviors and Propagation:
preventDefault
property on the input element event attribute. This is useful when only the event handler code should be run, but screen-update(s) should not happen.stopPropagation
attribute will keep parent event handlers from handling the same event type triggered by a descendant.Event Callbacks:
EventCallback<T>
that carries the data.[Parameter]
attribute on a public
property of type EventCallback<T>
where 'T' is the type of Event that is being captured.EventArgs
parameters to directly wire-up the event callback.Input and Form are common web elements to capture user inputs. Blazor adds-on to these with capabilities to validate and manipulate form inputs.
The EditForm:
<form>
element.OnSubmit
event.<input>
element to implement binding and validation features.To create an EditForm with Data Binding:
public class NewFormUser { public string UserName { get; set; } ...}
etc.@inject
any service that provides a factory for the class, or other functionality that supports the Form-generated class.OnInitializedAsync()
, or to handle changes in specific Input elements such as a button click or item selection.Each Blazor Input Component is rendered as a specific HTML element as explained in this table from MS Learn:
Input component | Rendered as (HTML) |
---|---|
InputCheckbox |
<input type="checkbox"> |
InputDate<TValue> |
<input type="date"> |
InputFile |
<input type="file"> |
InputNumber<TValue> |
<input type="number"> |
InputRadio<TValue> |
<input type="radio"> |
InputRadioGroup<TValue> |
Group of child radio buttons |
InputSelect<TValue> |
<select> |
InputText |
<input> |
InputTextArea |
<textarea> |
About Blazor Control Input Components:
@ref
for referencing C# variables.Declarative client-side validations are performed prior to sending to server-side.
Server-side enables handling complex validations:
EditForm Events:
OnValidSubmit
: Input fields all pass validation rules (attributes). Use for basic validation.OnInvalidSubmit
: One or more input fields fail validation rules. Use for basic validation.OnSubmit
: Regardless of valid/invalid state, this event is fired. Use when data should be processed by more complex validation functions.OnSubmit
is used when either/both of the other 2 are used as well.EditContext object:
submit
events.Model
field to retrieve the inputted values.Validation Failure Notification:
Validate user input as soon as possible in Blazor Forms:
Use the following to help users understand what is necessary:
Many annotations are available including:
Field Validation: User tabs out of a field.
Model Validation: User submits the form and validation is handled server-side.
<DataAnnotationsValidator />
: Checks the model annotations against the entered values.<ValidationSummary />
: Displays a summary of validation errors.To implement Field Validations (client-side):
[Required]
, [EmailAddress]
, [Range(int start, int stop)]`, etc.[Required(ErrorMessage = "Phone number is required")]
<DataAnnotationsValidator />
and <ValidationSummary />
Blazor Elements to the <EditForm>
so that the validation attributes are utilized.To implement Model Validations (server-side) using OnSubmit
:
EditContext
parameter for checking input data: editContext.Validate()
.<ValidationMessage For="@(() => model.Property)" />
to execute a lambda indicating validation errors.To implement Model Validations (server-side) using OnValidSubmit
and OnInvalidSubmit
:
OnValidSubmit=@OnValidHandler
and OnInvalidSubmit=@OnInvalidHandler
to the <EditForm>
attributes.OnValidSubmit()
and OnInvalidSubmit()
handlers that take EditContext editContext
as a parameter.OnValidSubmit()
implement storing the valid data in the handler.OnInvalidSubmit()
impelment activating an error message explaining what the user needs to do.Create one only if the existing Validation Attributes are not sophisticated enough to validate a particular case:
ValidationAttribute
.IsValid
method.IsValid
method.When using OnValidSubmit()
and OnInvalidSubmit()
, server-side validation is triggered:
<DataAnnotationsValidator />
element.<ValidationSummary />
.Use JS Interop to execute JavaScript Libraries and functions from Blazor apps.
<script>
tag to load JavaScript code in Blazor. Can be a .js
file ref or in-line JS code.<script src="_framework/blazor.*.js"></script>
block in Pages/_Host.cshtml
.<head>
element of a page because Blazor only controls content within the <body>
element.IJSRuntime
to call a JavaScript function from .NET code by @inject
ing into the Component.InvokeAsync
and InvokeVoidAsync
become available to run JavaScript code. Use await
as per usual.If SignalR has not made a connection between the server and the web page, JS Interop will fail.
OnAfterRender
or OnAfterRenderAsync
to detect when rendering has completed.div @ref="placeHolder"></div>
so that Blazor does not attempt to track its contents. Add JavaScript elements here to avoid corrupting server-side DOM copy.ElementReference
holds the reference to the empty element as a field.Use invokeMethod
and invokeMethodAsync
helper functions. The async version returns a JavaScript promise
.
Task
or Task<T>
where T is JsonSerializable.JSInvokable
attribute allows specifying an alias for the method reference name.DotNetObjectReference
is provided by JS Interop to create an object ref in .NET code so it can be safely passed to JavaScript code.Tracks a Component from creation through destruction:
Handle an Event by overriding the corresponding method.
Multiple Render cycles might happen before a page finishes rendering.
A ParameterView
object is used to pass parameters between Components.
The SetParametersAsync
method accepts ParameterView
in its argument list. Therefore, calling base.SetParametersAsyc
with your own parameters will inject new "state" into the initializing Component. Also, any processing the parameters need prior to this Component rendering them should be done here.
Overriding Lifecycle Parameters is a common practice to handle specific needs at various places during the Component lifecycle.
Use SetParametersAsync
to reinitialize a Component when the paremeters change.
Set render-mode
in Pages/_Host.cshtml
controls which Component Lifecycle Methods are available:
Server
: OnInitialized
and OnInitializedAsync
methods run only once per instance.ServerPrerendered
: OnInitialized
and OnInitializedAsync
methods run twice: Once during prerender phase (generates static page output), and again when SignalR connects with the browser. Therefore when retreiving or processing data in this method overrides, cache the operation output the first time and avoid running it again to reduce UI load latency.Objects that are injected by Component dependencies can be used within OnInitialized
and OnInitializedAsync
methods but not before.
Note: Calls to JavaScript code will not work during prerender phase. Instead, add JS Interop processing to OnAfterRender
and OnAfterRenderAsync
method overrides.
Use OnParametersSet
and OnParamtersSetAsync
to complete initialization tasks taht depend on compontn parameter values i.e. Calculating values for display or instance properties. Keep the processing short to avoid UI load latency.
About OnAfterRender
and OnAfterRenderAsync
:
OnInitialized
or OnInitializedAsync
, or OnParametersSet
or OnParametersSetAsync
methods run.StateHasChanged()
Component method.Order of invokation following call to StateHasChanged()
:
StateHasChanged
: Component is marked for rerendering.ShouldRender
: Getter returns boolean 'should render'. This method can be overridden but must return boolean.BuildRenderTree
: Renders the component. Can be overriden to build the render tree in a custom way.About bool firstRender
:
OnAfterRender
and OnAfterRenderAsync
parameters list accepts bool firstRender
.firstRender
is true
th first time the method is run, and false
after that.Release any unmanaged resources by implementing one or both of these methods.
The SignalR connection will be closed if an Exception is thrown!
Blazor stops functioning at this point.
Be sure to handle exceptions as part of the logic of Lifecycle Methods to ensure the SignalR connection remains open!
Overview:
Render Fragment type:
RenderFragment
: A placeholder object where teamplate markup is applied at Run Time.ChildContent
: The default name for a Render Fragment parameter. Custom naming is allowed.Generic RenderFragment<T>
Parameters:
To implement:
using
statement pointing to the namespace where the Component Fragment is stored.@typeparam
directive to introduce the type parameter. Multiple per template are allowed.Share and reuse UI Components using Razor Class Libraries.
Supports generation of rendered and static content across Blazor applications.
Razor Class Libraries are composed of:
Libraries can be bundled as NeGet Packages for distribution.
A new blank Razor Component Library component:
dotnet new razorclasslib -o {ProjectName}
.wwwroot
folder where images, JavaScript, and other static content should be stored and acts as the base relative path for references such as scripts.wwwroot
is /_content/{PACKAGE_ID}/{PATH_AND_FILENAME_INSIDE_WWWROOT}
Microsoft.AspNetCore.Components.Web
.Referencing A Razor Class Library:
dotnet add reference{ClassLibraryNameAndPath}
.dotnet add package {ClassLibraryName}
.@using
to point to the namespace of the Razor Library component._Imports.razor
.Control Rendering Mode:
<MyChildComponent @rendermode="InteractiveServer" />
Define properties in the CSPROJ file of the Razor Class Library:
Major.Minor.Patch-PrereleaseNameRevision
. Default is '1.0.0'.There are other properties but this is a good starting list.
Use dotnet pack
to pack the package, or set <GeneratePackageOnBuild>
element to True
to tell the Pack service to create a NuGet Package during build
. It will be output to bin\Debug
as a .nupkg
file.
Note: Use these same steps in CI-CD pipleline to generate and package a NuGet package to NuGet.org, another GitHub repo, or another location.
Note: Publishing a version that includes additional version information beyond Patch
will be read by the NuGet Package Import Process. An importing project might return "error: There are no stable versions available {version} is the best available. Consider adding the --prerelease option". If this is not desireable, don't publish versions with a prerelease marker.
An X.509 Certificate for code signing
and time stamping
will be necessary for publishing the package and enabling successful release
package importations.
Program.cs
like builder.Services.AddSingleton<DataGetterService>();
.@inject
.DbSet<T>
allows adding an ASP.NET Controller that can access the database data.Controller
, be sure to use cURL
or similar to query the path and see what is actually returned. A common scenario is routing is not correct and the returned page is not the MVC Controller content, but an error page.Microsoft.AspNetCore.Components.EventCallback
. This is an event handler delegate type (struct) that can be used to cast an event handler (i.e. onclick() or lambda) to a parameter on the Fragment/Child Razor Component. Similar to passing a function using React props
.@inject IJSRuntime JS
in the top portion of a Razor Page. Utilize the JS
Runtime code like you would anywhere else, such as in an event handler: await JS.InvokeVoidAsync("alert", msg);
. This is called JavaScript Interop, and it enables doing things in JS, that Blazor cannot do natively.InvokeAsync<T>()
to run JavaScript code, it might be necessary to include triple quotes, which enforces a literal string to be passed, for times when embedded quote marks are necessary in the encapsulated JavaScript code.<script>
element, reference a JavaScript library on the file system within a <script>
element, or reference a remote JavaScript package by referencing a web url i.e. <script src="https://cdn.jsdelivr.net/npm/package@latest/dist/thePackage.min.js"></script>
.@implements
, such as @implements IDisposable
, then implement the required method(s).StateHasChanged()
, a Component built-in method!@inject
syntax instead.Get started developing an API with just a few lines of code, as opposed to the many files and code blocks necessary using previous templates and frameworks of DotNET.
AddControllers()
).MapGet()
, MapPost()
, MapPut()
, and MapDelete()
.app.Run()
starts the API so it is 'listening' for requests.Run dotnet new web -o $projectName -f net6.0
to get started.
Use dotnet run
just like any other project to launch it.
The default TCP port is a value between 5000 and 5300. SSL ports should be in the 7000 to 7300 range.
Swagger is a documentation tool.
Whenever changes happen to an API, documentation should change too!
Self-documenting can be helpful, reducing costs of maintaining code during the development process.
How to install Swagger: dotnet add package Swashbuckle.AspNetCore --version 6.1.4
.
Configure Swagger:
Microsoft.OpenApi.Models;
.UseSwagger()
and UseSwaggerUI()
(see example below):// Set SwaggerGen method
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(
sg => {
sg.SwaggerDoc(
"v1", new OpenApiInfo {
Title="title",
Description="description",
Version="v1"
});
});
// Add UseSwagger and UseSwaggerUI
app.UseSwagger();
app.UseSwaggerUI(
usui => {
usui.SwaggerEndpoint("/swagger/v1/swagger.json", "version");
});
Major use cases:
app.MapGet()
function.// client sends an HTTP GET verb with the URI "/products/1"
// id param will capture the route parameter '1' in the Minimal API route
app.MapGet("/products/{id}", (int id) => data.SingleOrDefault(product => product.Id == id));
POST will create a resoruces.
// assume a Product Class like
public record Product(int Id, string Name, string Description);
// JSON BODY with POST request:
// {
// "Name": "New product",
// "Description": "product description"
// }
app.MapPost("/products", (Product product) => { /* process BODY here */ });
PUT means "update an existing record".
MapPut()
.MapPost()
.app.MapPut("/products", (Product product) => { /* update the data store using this product instance */ });
Use MapDelete()
.
IsDeleted
from false
to true
.Minimal API framework recognizes Types and serializes them as JSON.
ASP.NET Core and .NET 6 support local DB storage ORMs like Entity Framework Core.
EF uses entity classes and a context object to represent a session with a database.
The Entity Class:
The Context Class:
Delegate DB operations to the Context class.
Queries are always executed against the DB.
Queries are:
FindAsync(param)
to get data by Id or other Entity field(s).Remove(param)
method.Item
(see in-memory db) and IsComplete
fields to make changes to the instance, then use db.SaveChangesAsync()
to commit the changed entity back to the DB.Terminal: dotnet add package Microsoft.EntityFrameworkCore.InMemory --version 6.0
Once installed:
Program.cs
and any entity models e.g. Pizza.cs
: using Microsoft.EntityFrameworkCore
.builder.Services.AddDbContext<YourEntity>(options => options.UseInMemoryDatabase("items"));
.class YourEntityDB : DbContext
{
public YourEntityDb(DbContentOptions options) : base(options) { }
public DbSet<YourEntity> YourEntity { get; set; } = null!;
}
Connect to a relational database that will persist data between application stop-starts:
dotnet add package Microsoft.EntityFrameworkCore.Sqlite --version 6.0
.dotnet tool install --global donet-ef
.dotnet add package Microsoft.EntityFrameworkCore.Design --version 6.0
.First, add a builder to Program.cs:
// check config for connection string 'pizzas' and if missing use the datasource instead
var connectionString = builder.Configuration.GetConnectionString("Pizzas") ?? "Data Source=Pizzas.db";
Next define SQLite as the database type:
// YourEntityDb is the Class that inherits from 'DbContext' in a separeate Class file
builder.Services.AddSqlite<YourEntityDb>(connectionString);
Update migration using the EFC Migration Tool: dotnet ef migrations add InitialCreate
.
Create DB and Schema: dotnet ef database update
.
Verify:
YourEntities.db
appears in the filesystem.GET: app.MapGet("/things", async (ThingDb db) => await db.Things.ToListAsync());
POST:
app.MapPost("/thing", async (ThingDb db, Thing thing) =>
{
await db.Things.AddAsync(thing);
await db.SaveChangesAsync();
return Results.Created($"/thing/{thing.Id}", thing);
});
GET (by ID):
app.MapGet("/thing/{id}", async (ThingDb db, int id) =>
{
var thing = await db.Things.FindAsync(id);
if (thing is null)
{
return Results.NotFound();
}
else
{
return Results.Json(thing);
}
});
PUT (Update):
app.MapPut("/thing", async (ThingDb db, Thing updateThing, int id) =>
{
var thing = await db.Things.FindAsync(id);
if (thing is null)
{
return Results.NotFound();
}
thing.Name = updateThing.Name;
thing.Description = updateThing.Description;
await db.SaveChangesAsync();
return Results.NoContent();
});
DELETE:
app.MapDelete("/thing/{id}", async (ThingDb db, int id) =>
{
var thing = await db.Things.FindAsync(id);
if (thing is null)
{
return Results.NotFound();
}
db.Things.Remove(thing);
await db.SaveChangesAsync();
return Results.Ok();
});
Create a full stack webapp using:
Users manage data on the app front-end:
Ideas that guide:
Rules and guidelines:
Collections of Components:
Design Tokens: Names representing hard-coded values for visual elments e.g. Spacing, Color, Typography.
CSS Styles:
CSS in Javascript:
Component Composability:
Typography:
Icons:
An SPA:
React:
Angular:
Vue.js:
Svelte:
Next.js:
Takes JS, CSS, images, etc and creates one or more 'bundled files'.
Used to service HTTP requests while developing the App. SPAs are hosted applications - a server on an HTTP port and possibly behind a proxy will serve up content based on browser requests.
Configurable settings:
One-way:
React UseState:
onClick
to tell the Parent to update the data.This needs to be planned to ensure effecient component rendering:
useState
hook.useState
'hooks' into the Component lifecycle to add functionality.Using Vite:
npm create vite@latest ProjectName --template react
npm install
.vite.config.js
to set a static server port. 3000 is suggested.npm run dev
and open the URI in a browser to see the rendered site.The Full-stack learning module used a GitHub CodeSpace as a development container. This section will contain notes about CodeSpaces and my experience using them.
CodeSpaces are not free, at least after 60 hours per month:
Go to the CodeSpaces Dashboard and find the CodeSpace to clean up and click the Delete button.
At the very least, 'Stop' the CodeSpace until you need it next.
ASP.NET Core Blazor Event Handling.
ASP.NET Core Blazor Forms Overview.
ASP.NET Core IComponent Interface.
ASP.NET Core ComponentBase class.
Take a peek at the .NET Team's GitHub project dotnet-presentations blazor-workshop repo.
Return to ContEd Index
Return to Root README