« Blazor ASP.NET Core 7.0 » : différence entre les versions

De Banane Atomic
Aller à la navigationAller à la recherche
Ligne 245 : Ligne 245 :
# available providers: cdnjs, jsdelivr, unpkg, filesystem
# available providers: cdnjs, jsdelivr, unpkg, filesystem


# install bootstrap, only bootstrap.min.css and bootstrap.bundle.min.js
# install bootstrap, only bootstrap.min.css and bootstrap.bundle.min.js (with popper)
libman install bootstrap@5.0.1 --files dist/css/bootstrap.min.css --files dist/js/bootstrap.bundle.min.js
libman install bootstrap@5.0.1 \
--files dist/css/bootstrap.min.css \
--files dist/css/bootstrap.min.css.map \
--files dist/js/bootstrap.bundle.min.js \
--files dist/js/bootstrap.bundle.min.js.map
# default destination path: wwwroot/lib/bootstrap
# default destination path: wwwroot/lib/bootstrap



Version du 28 mars 2023 à 13:07

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) { }

CSS isolation

Pages/Items.razor.css
/* scoped css only for this page */
/* @import is not allowed in scoped css */

JS isolation

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();
    }

Form

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() { }

Input Text

Razor.svg
<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

Data Annotation

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

Data binding

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";
}

Dependency injection

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.

Entity Framework Core

Using a DbContext factory

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

LibMan library manager

Bash.svg
# 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 (with popper)
libman install bootstrap@5.0.1 \
--files dist/css/bootstrap.min.css \
--files dist/css/bootstrap.min.css.map \
--files dist/js/bootstrap.bundle.min.js \
--files dist/js/bootstrap.bundle.min.js.map
# 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

Turn on detailed errors

Program.cs
builder.Services.AddServerSideBlazor()
                .AddCircuitOptions(o =>
    {
        o.DetailedErrors = true;
        o.DetailedErrors = builder.Environment.IsDevelopment();  // only add details when debugging
    });