« Blazor ASP.NET Core 6.0 » : différence entre les versions
De Banane Atomic
Aller à la navigationAller à la recherche
(41 versions intermédiaires par le même utilisateur non affichées) | |||
Ligne 154 : | Ligne 154 : | ||
items = await this.ItemClient.GetAsync(); | items = await this.ItemClient.GetAsync(); | ||
} | } | ||
protected override async Task OnAfterRenderAsync(bool firstRender) { } | |||
} | } | ||
</filebox> | </filebox> | ||
* [https://docs.microsoft.com/en-us/aspnet/core/blazor/fundamentals/routing?view=aspnetcore- | * [https://docs.microsoft.com/en-us/aspnet/core/blazor/fundamentals/routing?view=aspnetcore-6.0#route-parameters Route parameters] | ||
* [https://docs.microsoft.com/en-us/aspnet/core/blazor/fundamentals/routing?view=aspnetcore- | * [https://docs.microsoft.com/en-us/aspnet/core/blazor/fundamentals/routing?view=aspnetcore-6.0#route-constraints Route constraints] | ||
* [https://docs.microsoft.com/en-us/aspnet/core/blazor/components/lifecycle?view=aspnetcore-6.0 Component lifecycle] | |||
== [https://docs.microsoft.com/en-us/aspnet/core/blazor/components/css-isolation?view=aspnetcore-6.0 CSS isolation] == | |||
<filebox fn='Pages/Items.razor.css'> | <filebox fn='Pages/Items.razor.css'> | ||
/* scoped css only for this page */ | /* scoped css only for this page */ | ||
/* @import is not allowed in scoped css */ | |||
</filebox> | |||
== [https://docs.microsoft.com/en-us/aspnet/core/blazor/javascript-interoperability/call-javascript-from-dotnet?view=aspnetcore-6.0#javascript-isolation-in-javascript-modules JS isolation] == | |||
<filebox fn='Pages/MyPage.razor.cs'> | |||
public partial class MyPage : ComponentBase, IAsyncDisposable | |||
{ | |||
[Inject] | |||
private IJSRuntime JS { get; set; } = default!; | |||
private IJSObjectReference? module; | |||
public async Task CallMyJsFunction() | |||
{ | |||
if (module is not null) | |||
await module.InvokeVoidAsync("myFunction"); | |||
} | |||
protected override async Task OnAfterRenderAsync(bool firstRender) | |||
{ | |||
if (firstRender) | |||
{ | |||
module = await JS.InvokeAsync<IJSObjectReference>("import", "./Pages/MyPage.razor.js"); | |||
await CallMyJsFunction(); | |||
} | |||
} | |||
async ValueTask IAsyncDisposable.DisposeAsync() | |||
{ | |||
if (module is not null) | |||
await module.DisposeAsync(); | |||
} | |||
} | |||
</filebox> | |||
<filebox fn='Pages/MyPage.razor.js'> | |||
export function myFunction() { } | |||
</filebox> | </filebox> | ||
= Static files = | = Static files = | ||
Ligne 195 : | Ligne 235 : | ||
# uninstall bootstrap | # uninstall bootstrap | ||
libman uninstall bootstrap@5.0.1 | libman uninstall bootstrap@5.0.1 | ||
</kode> | |||
= [https://docs.microsoft.com/en-us/aspnet/core/blazor/components/event-handling?view=aspnetcore-6.0 Event handling] = | |||
<kode lang='cshtml'> | |||
<button @onclick=OnButtonClicked>Click me</button> | |||
<button @onclick="() => OnButtonClicked(Item.Name)">Click me</button> | |||
@code { | |||
void OnButtonClicked() | |||
{ } | |||
async Task OnButtonClicked(string name) | |||
{ } | |||
} | |||
</kode> | |||
== preventDefault and stopPropagation == | |||
<kode lang='cshtml'> | |||
<div @onclick="OnDivClicked"> | |||
<a href="/page1" @onclick=OnLinkClicked | |||
@onclick:preventDefault @* prevent the navigation to the url but still executes the OnLinkClicked method *@ | |||
@onclick:stopPropagation> @* stop the propagation of the event (bubbling), so the OnDivClicked method is never called *@ | |||
Page 1 | |||
</a> | |||
</div> | |||
</kode> | </kode> | ||
Ligne 266 : | Ligne 331 : | ||
} | } | ||
</filebox> | </filebox> | ||
== [https://learn.microsoft.com/en-us/aspnet/core/blazor/forms-and-input-components?view=aspnetcore-6.0#data-annotations-validator-component-and-custom-validation Form validation] == | |||
<filebox fn='Item.cs'> | |||
[Required, MinLength(4)] | |||
public string Name { get; set; } | |||
</filebox> | |||
<filebox fn='Pages/ItemEdit.razor'> | |||
<EditForm Model="@Item"> | |||
<DataAnnotationsValidator /> | |||
@* affiche la liste des messages d'erreur de validation *@ | |||
<ValidationSummary /> | |||
<div class="form-group row"> | |||
<label for="name" class="col-sm-3">Name: </label> | |||
<InputText id="name" @bind-value="Item.Name" class="form-control col-sm-8" placeholder="Enter name"></InputText> | |||
@* affiche le message d'erreur de validation *@ | |||
<ValidationMessage class="offset-sm-3 col-sm-8" For="@(() => Item.Name)" /> | |||
</div> | |||
</filebox> | |||
== [https://github.com/dotnet/aspnetcore/blob/master/src/Components/Web/src/Forms/InputText.cs Input Text] == | |||
<kode lang='razor'> | |||
<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> | |||
@* ValueChanged event *@ | |||
<InputText Value="@item.Name" | |||
ValueChanged="ItemNameChanged" | |||
ValueExpression="() => Item.Name"> | |||
</InputText> | |||
</kode> | |||
<kode lang='cs'> | |||
private void ItemNameChanged(string value) | |||
{ | |||
item.Name = value; | |||
} | |||
</kode> | |||
== Input Checkbox == | == Input Checkbox == | ||
Ligne 297 : | Ligne 411 : | ||
</kode> | </kode> | ||
= [https://docs.microsoft.com/en-us/aspnet/core/ | = Javascript interop = | ||
== [https://docs.microsoft.com/en-us/aspnet/core/blazor/call-javascript-from-dotnet Call JS from .NET] == | |||
<filebox fn='Pages/MyPage.razor.cs'> | |||
[Inject] | |||
public IJSRuntime JS { get; set; } | |||
// one way to import mypage.js | |||
await JS.InvokeVoidAsync("import", "./js/mypage.js"); | |||
// invoke JavaScript functions without reading a returned value | |||
await JS.InvokeVoidAsync("MyJSMethod", "arg1"); | |||
// pass an array as argument | |||
await JS.InvokeVoidAsync("MyJSMethod", (object)myArray); | |||
// invoke JavaScript functions and read a returned value | |||
var result = await JS.InvokeAsync<string>("AddExclamationPoint", "arg1"); | |||
</filebox> | |||
<filebox fn='wwwroot/js/mypage.js'> | |||
window.MyJsMethod = function(arg1) { | |||
alert(arg1); | |||
} | |||
window.AddExclamationPoint = function(arg1) { | |||
return arg1 + ' !'; | |||
} | |||
</filebox> | |||
Another way to import mypage.js | |||
<filebox fn='Pages/_Layout.cshtml'> | |||
<body> | |||
@* at the end of body *@ | |||
<script src="js/mypage.js"></script> | |||
</body> | |||
</filebox> | |||
== [https://docs.microsoft.com/en-us/aspnet/core/blazor/call-dotnet-from-javascript Call .NET from JS] == | |||
<filebox fn='wwwroot/js/site.js'> | |||
window.UpdateButtonContent = function (dotnetHelper, newContent) { | |||
dotnetHelper.invokeMethodAsync('UpdateButtonContent', newContent); | |||
dotnetHelper.dispose(); | |||
}; | |||
</filebox> | |||
<filebox fn='Pages/MyPageBase.cs'> | |||
var objRef = DotNetObjectReference.Create(this); | |||
await JSRuntime.InvokeVoidAsync("UpdateButtonContentJS", objRef, newContent); | |||
objRef.Dispose(); | |||
[JSInvokable] | |||
public void UpdateButtonContent(string newContent) | |||
{ | |||
ButtonContent = newContent; | |||
} | |||
</filebox> | |||
<filebox fn='Pages/_Host.cshtml'> | |||
<body> | |||
@* à la fin de body *@ | |||
<script src="js/myscript.js"></script> | |||
</body> | |||
</filebox> | |||
= [https://learn.microsoft.com/en-us/aspnet/core/blazor/fundamentals/dependency-injection?view=aspnetcore-6.0 Dependency injection] = | |||
<filebox fn='Program.cs'> | |||
builder.Services.AddSingleton<IDataAccess, DataAccess>(); | |||
</filebox> | |||
<filebox fn='Pages/MyPage.razor.cs'> | |||
[Inject] | |||
private IDataAccess DataAccess { get; set; } | |||
</filebox> | |||
= [https://www.lightgalleryjs.com LightGallery] = | |||
<kode lang='bash'> | |||
# download lightgallery | |||
libman install lightgallery@2.3.0 \ | |||
--files lightgallery.min.js \ | |||
--files plugins/thumbnail/lg-thumbnail.min.js \ | |||
--files css/lightgallery.min.css \ | |||
--files css/lg-thumbnail.min.css \ | |||
--files fonts/lg.woff2 \ | |||
--files images/loading.gif | |||
</kode> | |||
<filebox fn='Pages/_Layout.cshtml'> | |||
<head> | |||
<link rel="stylesheet" href="lib/lightgallery/css/lightgallery-bundle.min.css" /> | |||
@* or *@ | |||
<link rel="stylesheet" href="lib/lightgallery/css/lightgallery.min.css" /> | |||
<link rel="stylesheet" href="lib/lightgallery/css/lg-thumbnail.min.css" /> | |||
</head> | |||
<body> | |||
<script src="lib/lightgallery/lightgallery.min.js"></script> | |||
<script src="lib/lightgallery/plugins/thumbnail/lg-thumbnail.min.js"></script> | |||
<script src="js/site.js"></script> | |||
</body> | |||
</filebox> | |||
<filebox fn='Pages/LightGallery.razor'> | |||
<button id="open-gallery">Open gallery</button> | |||
<button @onclick=OpenGallery>Open gallery</button> | |||
</filebox> | |||
<filebox fn='Pages/LightGallery.razor.cs'> | |||
[Inject] | |||
private IJSRuntime JS { get; set; } | |||
protected override async Task OnAfterRenderAsync(bool firstRender) | |||
{ | |||
if (firstRender) | |||
{ | |||
await JS.InvokeVoidAsync("import", "./lib/lightgallery/lightgallery.min.js"); | |||
await JS.InvokeVoidAsync("import", "./lib/lightgallery/plugins/thumbnail/lg-thumbnail.min.js"); | |||
await JS.InvokeVoidAsync("import", "./js/lightgallery.js"); | |||
} | |||
} | |||
private async Task OnProfileNameClicked(string profileName) | |||
{ | |||
await JS.InvokeVoidAsync("OpenGallery", (object)pictureUrls); | |||
} | |||
</filebox> | |||
<filebox fn='wwwroot/js/lightgallery.js'> | |||
const $button = document.getElementById('open-gallery'); | |||
const dynamicGallery = lightGallery($button, { | |||
dynamic: true, | |||
dynamicEl: [ | |||
{ | |||
src: 'img/1.jpg', | |||
thumb: 'img/thumb1.jpg', | |||
subHtml: '<h4>Image 1 title</h4><p>Image 1 descriptions.</p>', | |||
}, | |||
{ | |||
src: 'img/2.jpg', | |||
thumb: 'img/thumb2.jpg', | |||
subHtml: '<h4>Image 2 title</h4><p>Image 2 descriptions.</p>', | |||
} | |||
], | |||
mode: 'lg-fade', | |||
plugins: [lgThumbnail], | |||
}); | |||
$button.addEventListener('click', function () { | |||
dynamicGallery.openGallery(); | |||
}); | |||
</filebox> | |||
<filebox fn='wwwroot/js/site.js'> | |||
window.OpenGallery = function (picturePaths) { | |||
let elements = picturePaths.map(x => ({ | |||
src: x, | |||
thumb: x | |||
})); | |||
let gallery = lightGallery(document.getElementsByTagName('main')[0], { | |||
dynamic: true, | |||
dynamicEl: elements, | |||
mode: 'lg-fade', | |||
plugins: [lgThumbnail], | |||
}); | |||
gallery.openGallery(); | |||
} | |||
</filebox> | |||
= Infinite scrolling = | |||
* [https://www.meziantou.net/infinite-scrolling-in-blazor.htm Infinite scrolling in Blazor] | |||
* [https://github.com/SveNord/Sve-Blazor-InfiniteScroll Sve-Blazor-InfiniteScroll] | |||
== [https://github.com/ljbc1994/BlazorIntersectionObserver BlazorIntersectionObserver] == | |||
<kode lang='bash'> | |||
dotnet add package BlazorIntersectionObserver | |||
</kode> | |||
<filebox fn='Program.cs'> | |||
builder.Services.AddIntersectionObserver(); | |||
</filebox> | |||
<filebox fn='ItemGallery.razor'> | |||
<div class="d-flex flex-wrap"> | |||
@foreach (var item in Items) | |||
{ | |||
<div class="card mx-5 my-2"> | |||
<img src=@item.Cover class="card-img-top"> | |||
</div> | |||
} | |||
</div> | |||
<IntersectionObserve OnChange="@OnIntersectingChanged"> | |||
<div @ref="context.Ref.Current" class="@(allItemsLoaded || context.IsIntersecting ? "d-none": "")"> | |||
<div class="spinner-border d-block mx-auto" role="status"> | |||
<span class="sr-only">Loading...</span> | |||
</div> | |||
</div> | |||
</IntersectionObserve> | |||
</filebox> | |||
<filebox fn='ItemGallery.razor.cs'> | |||
private bool allProfilesLoaded; | |||
private async Task OnIntersectingChanged(IntersectionObserverEntry entry) | |||
{ | |||
if (entry.IsIntersecting) | |||
{ | |||
CurrentItemQuery.PageIndex++; | |||
var fetchedItems = await this.ProfileClient.GetAsync(CurrentItemQuery); | |||
if (fetchedItems.Count == 0) | |||
allProfilesLoaded = true; | |||
else | |||
Items.AddEach(fetchedItems); | |||
} | |||
} | |||
</filebox> | |||
= [[Asp.net_core_6#Migrate_from_ASP.NET_Core_5.0_to_6.0|Migrate from ASP.NET Core 5.0 to 6.0]] = | |||
= [https://stackoverflow.com/questions/57514541/how-to-turn-on-circuitoptions-detailederrors Turn on detailed errors] = | |||
<filebox fn='Program.cs'> | |||
builder.Services.AddServerSideBlazor() | |||
.AddCircuitOptions(o => | |||
{ | |||
o.DetailedErrors = true; | |||
}); | |||
</filebox> |
Dernière version du 27 mars 2023 à 11:07
Links
Create a new app
# Blazor server dotnet new blazorserver -o [project-name] --no-https cd [project-name] dotnet run |
Solution Blazor / Webapi
# create item/item.sln dotnet new sln -o item cd item dotnet new blazorserver -o item.blazor --no-https dotnet new webapi -o item.webapi --no-https dotnet sln add item.blazor/item.blazor.csproj dotnet sln add item.webapi/item.webapi.csproj |
item/.vscode/tasks.json |
{ "version": "2.0.0", "tasks": [ { "label": "build blazor", "command": "dotnet", "type": "process", "args": [ "build", "${workspaceFolder}/item.blazor/item.blazor.csproj", "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary" ], "problemMatcher": "$msCompile" }, { "label": "build webapi", "command": "dotnet", "type": "process", "args": [ "build", "${workspaceFolder}/item.webapi/item.webapi.csproj", "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary" ], "problemMatcher": "$msCompile" }, { "label": "build all", "dependsOn": [ "build blazor", "build webapi" ], "group": "build", "problemMatcher": [] } ] } |
item/.vscode/launch.json |
{ "version": "0.2.0", "configurations": [ { "name": "blazor", "type": "coreclr", "request": "launch", "preLaunchTask": "build blazor", "program": "${workspaceFolder}/item.blazor/bin/Debug/net6.0/item.blazor.dll", "args": [], "cwd": "${workspaceFolder}/item.blazor", "stopAtEntry": false, "serverReadyAction": { "action": "openExternally", "pattern": "\\bNow listening on:\\s+(https?://\\S+)" }, "env": { "ASPNETCORE_ENVIRONMENT": "Development" }, "sourceFileMap": { "/Views": "${workspaceFolder}/Views" } }, { "name": "webapi", "type": "coreclr", "request": "launch", "preLaunchTask": "build webapi", "program": "${workspaceFolder}/item.webapi/bin/Debug/net6.0/item.webapi.dll", "args": [], "cwd": "${workspaceFolder}/item.webapi", "stopAtEntry": false, "serverReadyAction": { "action": "openExternally", "pattern": "\\bNow listening on:\\s+(https?://\\S+)" }, "env": { "ASPNETCORE_ENVIRONMENT": "Development" }, "sourceFileMap": { "/Views": "${workspaceFolder}/Views" } } ], "compounds": [ { "name": "blazor / webapi", "configurations": [ "blazor", "webapi" ], "preLaunchTask": "build all" } ] } |
Pages
Pages/Items.razor |
@* url *@ @page "/items" @* url with a parameter *@ @page "/item/{id:int}" <PageTitle>Items</PageTitle> @* wait for the Items to be loaded *@ @if (items == null) { <p><em>Loading...</em></p> } else { /* ... */ } |
Pages/Items.razor.cs |
public partial class Items : 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.cs |
public partial class MyPage : ComponentBase, IAsyncDisposable { [Inject] private IJSRuntime JS { get; set; } = default!; private IJSObjectReference? module; public async Task CallMyJsFunction() { if (module is not null) await module.InvokeVoidAsync("myFunction"); } protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { module = await JS.InvokeAsync<IJSObjectReference>("import", "./Pages/MyPage.razor.js"); await CallMyJsFunction(); } } async ValueTask IAsyncDisposable.DisposeAsync() { if (module is not null) await module.DisposeAsync(); } } |
Pages/MyPage.razor.js |
export function myFunction() { } |
Static files
Pages/_Layout.cshtml |
<head> <link href="css/site.css" rel="stylesheet" /> <link href="{AssemblyName}.styles.css" rel="stylesheet" /> @* active CSS isolation *@ |
wwwroot/css/site.css |
/* only CSS for the entire app, not a specific page */ |
- wwwroot/favicon.ico
LibMan
LibMan is a library manager
# 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 |
Event handling
<button @onclick=OnButtonClicked>Click me</button> <button @onclick="() => OnButtonClicked(Item.Name)">Click me</button> @code { void OnButtonClicked() { } async Task OnButtonClicked(string name) { } } |
preventDefault and stopPropagation
<div @onclick="OnDivClicked"> <a href="/page1" @onclick=OnLinkClicked @onclick:preventDefault @* prevent the navigation to the url but still executes the OnLinkClicked method *@ @onclick:stopPropagation> @* stop the propagation of the event (bubbling), so the OnDivClicked method is never called *@ Page 1 </a> </div> |
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 | @* by default uses the on change event (focus lost) *@ <input type="text" @bind=Description /> @* change to on input event *@ <input type="text" @bind=Description @bind:event="oninput" /> @code { string Description { get; set; } = "Text"; } |
Form
EditItem.razor |
@page "/edititem/{ItemId:int}" @* to access to Model.Item *@ @using Model <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 IItemDataService ItemDataService { get; set; } private Item item = new Item(); 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 ItemDataService.UpdateItemAsync(item); } // when the submit button is clicked and the model is not valid private async Task HandleInvalidSubmitAsync() { } |
Form validation
Item.cs |
[Required, MinLength(4)] public string Name { get; set; } |
Pages/ItemEdit.razor |
<EditForm Model="@Item"> <DataAnnotationsValidator /> @* affiche la liste des messages d'erreur de validation *@ <ValidationSummary /> <div class="form-group row"> <label for="name" class="col-sm-3">Name: </label> <InputText id="name" @bind-value="Item.Name" class="form-control col-sm-8" placeholder="Enter name"></InputText> @* affiche le message d'erreur de validation *@ <ValidationMessage class="offset-sm-3 col-sm-8" For="@(() => Item.Name)" /> </div> |
Input Text
<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> @* ValueChanged event *@ <InputText Value="@item.Name" ValueChanged="ItemNameChanged" ValueExpression="() => Item.Name"> </InputText> |
private void ItemNameChanged(string value) { item.Name = value; } |
Input Checkbox
InputCheckbox requires a cascading parameter of type EditContext. For example, you can use InputCheckbox inside an EditForm. |
<div class="form-group row"> <label for="smoker" class=" offset-sm-3"> <InputCheckbox id="smoker" @bind-Value="Employee.Smoker"> </InputCheckbox> Smoker </label> </div> |
To have a checkbox without an EditForm use input type="checkbox" instead:
<label> <input type="checkbox" @onchange=OnCheckboxValueChanged /> Text ... </label> @code { private void OnCheckboxValueChanged(ChangeEventArgs e) { var checkboxValue = (bool)e.Value; } } |
Javascript interop
Call JS from .NET
Pages/MyPage.razor.cs |
[Inject] public IJSRuntime JS { get; set; } // one way to import mypage.js await JS.InvokeVoidAsync("import", "./js/mypage.js"); // invoke JavaScript functions without reading a returned value await JS.InvokeVoidAsync("MyJSMethod", "arg1"); // pass an array as argument await JS.InvokeVoidAsync("MyJSMethod", (object)myArray); // invoke JavaScript functions and read a returned value var result = await JS.InvokeAsync<string>("AddExclamationPoint", "arg1"); |
wwwroot/js/mypage.js |
window.MyJsMethod = function(arg1) { alert(arg1); } window.AddExclamationPoint = function(arg1) { return arg1 + ' !'; } |
Another way to import mypage.js
Pages/_Layout.cshtml |
<body> @* at the end of body *@ <script src="js/mypage.js"></script> </body> |
Call .NET from JS
wwwroot/js/site.js |
window.UpdateButtonContent = function (dotnetHelper, newContent) { dotnetHelper.invokeMethodAsync('UpdateButtonContent', newContent); dotnetHelper.dispose(); }; |
Pages/MyPageBase.cs |
var objRef = DotNetObjectReference.Create(this); await JSRuntime.InvokeVoidAsync("UpdateButtonContentJS", objRef, newContent); objRef.Dispose(); [JSInvokable] public void UpdateButtonContent(string newContent) { ButtonContent = newContent; } |
Pages/_Host.cshtml |
<body> @* à la fin de body *@ <script src="js/myscript.js"></script> </body> |
Dependency injection
Program.cs |
builder.Services.AddSingleton<IDataAccess, DataAccess>(); |
Pages/MyPage.razor.cs |
[Inject] private IDataAccess DataAccess { get; set; } |
LightGallery
# download lightgallery libman install lightgallery@2.3.0 \ --files lightgallery.min.js \ --files plugins/thumbnail/lg-thumbnail.min.js \ --files css/lightgallery.min.css \ --files css/lg-thumbnail.min.css \ --files fonts/lg.woff2 \ --files images/loading.gif |
Pages/_Layout.cshtml |
<head> <link rel="stylesheet" href="lib/lightgallery/css/lightgallery-bundle.min.css" /> @* or *@ <link rel="stylesheet" href="lib/lightgallery/css/lightgallery.min.css" /> <link rel="stylesheet" href="lib/lightgallery/css/lg-thumbnail.min.css" /> </head> <body> <script src="lib/lightgallery/lightgallery.min.js"></script> <script src="lib/lightgallery/plugins/thumbnail/lg-thumbnail.min.js"></script> <script src="js/site.js"></script> </body> |
Pages/LightGallery.razor |
<button id="open-gallery">Open gallery</button> <button @onclick=OpenGallery>Open gallery</button> |
Pages/LightGallery.razor.cs |
[Inject] private IJSRuntime JS { get; set; } protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { await JS.InvokeVoidAsync("import", "./lib/lightgallery/lightgallery.min.js"); await JS.InvokeVoidAsync("import", "./lib/lightgallery/plugins/thumbnail/lg-thumbnail.min.js"); await JS.InvokeVoidAsync("import", "./js/lightgallery.js"); } } private async Task OnProfileNameClicked(string profileName) { await JS.InvokeVoidAsync("OpenGallery", (object)pictureUrls); } |
wwwroot/js/lightgallery.js |
const $button = document.getElementById('open-gallery'); const dynamicGallery = lightGallery($button, { dynamic: true, dynamicEl: [ { src: 'img/1.jpg', thumb: 'img/thumb1.jpg', subHtml: '<h4>Image 1 title</h4><p>Image 1 descriptions.</p>', }, { src: 'img/2.jpg', thumb: 'img/thumb2.jpg', subHtml: '<h4>Image 2 title</h4><p>Image 2 descriptions.</p>', } ], mode: 'lg-fade', plugins: [lgThumbnail], }); $button.addEventListener('click', function () { dynamicGallery.openGallery(); }); |
wwwroot/js/site.js |
window.OpenGallery = function (picturePaths) { let elements = picturePaths.map(x => ({ src: x, thumb: x })); let gallery = lightGallery(document.getElementsByTagName('main')[0], { dynamic: true, dynamicEl: elements, mode: 'lg-fade', plugins: [lgThumbnail], }); gallery.openGallery(); } |
Infinite scrolling
BlazorIntersectionObserver
dotnet add package BlazorIntersectionObserver |
Program.cs |
builder.Services.AddIntersectionObserver(); |
ItemGallery.razor |
<div class="d-flex flex-wrap"> @foreach (var item in Items) { <div class="card mx-5 my-2"> <img src=@item.Cover class="card-img-top"> </div> } </div> <IntersectionObserve OnChange="@OnIntersectingChanged"> <div @ref="context.Ref.Current" class="@(allItemsLoaded || context.IsIntersecting ? "d-none": "")"> <div class="spinner-border d-block mx-auto" role="status"> <span class="sr-only">Loading...</span> </div> </div> </IntersectionObserve> |
ItemGallery.razor.cs |
private bool allProfilesLoaded; private async Task OnIntersectingChanged(IntersectionObserverEntry entry) { if (entry.IsIntersecting) { CurrentItemQuery.PageIndex++; var fetchedItems = await this.ProfileClient.GetAsync(CurrentItemQuery); if (fetchedItems.Count == 0) allProfilesLoaded = true; else Items.AddEach(fetchedItems); } } |
Migrate from ASP.NET Core 5.0 to 6.0
Turn on detailed errors
Program.cs |
builder.Services.AddServerSideBlazor() .AddCircuitOptions(o => { o.DetailedErrors = true; }); |