Pages
Pages/Items.razor
|
@page "/items" @* url *@
@page "/item/{id:int}" @* url avec un parameter *@
<PageTitle>Items</PageTitle>
@if (Items == null) @* attendre que la valeur soit chargée *@
{
<p><em>Loading...</em></p>
}
else
{ /* ... */ }
|
Pages/Items.razor.cs
|
public partial class Table : ComponentBase
{
private IReadOnlyCollection<ItemDto> items;
[Parameter]
public int Id { get; set; }
protected override async Task OnInitializedAsync()
{
items = await this.ItemClient.GetAsync();
}
protected override async Task OnAfterRenderAsync(bool firstRender) { }
|
Pages/Items.razor.css
|
/* scoped css only for this page */
/* @import is not allowed in scoped css */
|
Pages/MyPage.razor.js
|
export function myFunction() { }
export function myFunctionWithArg(arg) {
return 'ok';
}
|
Pages/MyPage.razor.cs
|
public partial class MyPage : ComponentBase, IAsyncDisposable
{
[Inject]
private IJSRuntime JS { get; set; } = default!;
private IJSObjectReference? module;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
module = await JS.InvokeAsync<IJSObjectReference>("import", "./Pages/MyPage.razor.js");
}
}
private async Task MyJsFunction()
{
if (module is not null)
await module.InvokeVoidAsync("myFunction");
}
private async ValueTask<string?> MyJsFunctionWithArg(string arg)
{
string result = null;
if (module is not null)
await module.InvokeAsync<string>("myFunctionWithArg", arg);
return result;
}
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (module is not null)
await module.DisposeAsync();
}
|
EditItem.razor
|
@page "/edititem/{ItemId:int}"
<EditForm Model="@item"
OnValidSubmit="@HandleValidSubmitAsync"
OnInvalidSubmit="@HandleInvalidSubmitAsync">
<button type="submit">Save</button>
<button @onclick="Cancel">Cancel</button>
</EditForm>
|
EditItem.razor.cs
|
[Parameter]
public int ItemId { get; set; }
[Inject]
private IItemService ItemService { get; set; }
private Item item = null;
protected override async Task OnInitializedAsync()
{
item = await ItemDataService.GetItemAsync(ItemId);
}
// when the submit button is clicked and the model is valid
private async Task HandleValidSubmitAsync()
{
await ItemService.UpdateItemAsync(item);
}
// when the submit button is clicked and the model is not valid
private async Task HandleInvalidSubmitAsync() { }
|
|
<div class="form-group row">
<label for="name"
class="col-sm-3">
Item:
</label>
<InputText id="name"
@bind-Value="Item.Name"
class="form-control col-sm-8"
placeholder="Enter name">
</InputText>
</div>
|
Form validation
Item.cs
|
[Required, StringLength(50, MinimumLength = 3, ErrorMessage = "Name must be between 3 and 50 characters")]
public string Name { get; set; }
|
Pages/ItemEdit.razor
|
<EditForm Model="@item">
<DataAnnotationsValidator />
<ValidationSummary /> @* affiche la liste des messages d'erreur de validation *@
<InputText @bind-value="item.Name"
placeholder="Enter name" />
<ValidationMessage class="offset-sm-3 col-sm-8" For="@(() => Item.Name)" /> @* affiche le message d'erreur de validation *@
|
One way
Changes made in the UI doesn't change the binded property.
The onchange event can be used to manually update the property.
Fichier:Blazor.svg
|
<input type="text" value="@Description"
@onchange=@((ChangeEventArgs __e) => Description = __e.Value.ToString())
@onchange=OnInputChanged />
@code {
string Description { get; set; } = "Text";
void OnInputChanged(ChangeEventArgs e) => Description = e.Value.ToString();
}
|
Two way
Fichier:Blazor.svg
|
<input type="text" @bind="Description" /> @* by default uses the 'on change' event (focus lost) *@
<input type="text" @bind="Description"
@bind:event="oninput" /> @* trigger the binding 'on input' event *@
@code {
string Description { get; set; } = "Text";
}
|
Program.cs
|
builder.Services.AddTransient<IMyService, MyService>();
|
Pages/MyPage.razor.cs
|
[Inject]
private IMyService myService { get; set; }
|
Lifetime
|
Description
|
Singleton |
creates a single instance of the service.
All components requiring a Singleton service receive the same instance of the service.
|
Transient |
creates a new instance of the service on each component request.
|
Scoped |
creates a new instance of the service on each HTTP request but not across SignalR connection/circuit messages.
Which means only when:
- The user closes the browser's window, then opens a new window and navigates back to the app.
- The user closes a tab of the app in a browser window, then opens a new tab and navigates back to the app.
- The user selects the browser's reload/refresh button.
|
DbContext isn't thread safe and isn't designed for concurrent use.
To handle multi-threads scenarios, use a AddDbContextFactory to create a DbContext for each query.
Program.cs
|
builder.Services.AddDbContextFactory<MyDbContext>();
|
MyRepository.cs
|
private readonly IDbContextFactory<MyDbContext> contextFactory;
public DataSummaryRepository(IDbContextFactory<MyDbContext> contextFactory)
{
this.contextFactory = contextFactory;
}
public async Task<Data> FetchDataAsync()
{
// create a DbContext for this query, then dispose it once the query has been executed
using var context = contextFactory.CreateDbContext();
}
|
External JS libraries
|
# install the LibMan CLI
dotnet tool install -g Microsoft.Web.LibraryManager.Cli
# create a libman.js file
libman init
# available providers: cdnjs, jsdelivr, unpkg, filesystem
# install bootstrap, only bootstrap.min.css and bootstrap.bundle.min.js
libman install bootstrap@5.0.1 --files dist/css/bootstrap.min.css --files dist/js/bootstrap.bundle.min.js
# default destination path: wwwroot/lib/bootstrap
libman install bootswatch@5.0.1 --files dist/darkly/bootstrap.min.css
# uninstall bootstrap
libman uninstall bootstrap@5.0.1
|
Program.cs
|
builder.Services.AddServerSideBlazor()
.AddCircuitOptions(o =>
{
o.DetailedErrors = true;
o.DetailedErrors = builder.Environment.IsDevelopment(); // only add details when debugging
});
|