ASP.NET valori e parametri di Blazor base

Nota

Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 8 di questo articolo.

Avviso

Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere Criteri di supporto di .NET e .NET Core. Per la versione corrente, vedere la versione .NET 8 di questo articolo.

Importante

Queste informazioni si riferiscono a un prodotto non definitive che può essere modificato in modo sostanziale prima che venga rilasciato commercialmente. Microsoft non riconosce alcuna garanzia, espressa o implicita, in merito alle informazioni qui fornite.

Per la versione corrente, vedere la versione .NET 8 di questo articolo.

Questo articolo illustra come eseguire il flusso di dati da un componente predecessore Razor ai componenti discendenti.

I valori e i parametri a catena offrono un modo pratico per eseguire il flusso dei dati in una gerarchia di componenti da un componente predecessore a un numero qualsiasi di componenti discendenti. A differenza dei parametri component, i valori e i parametri a catena non richiedono un'assegnazione di attributo per ogni componente discendente in cui vengono utilizzati i dati. I valori e i parametri a catena consentono anche ai componenti di coordinarsi tra loro in una gerarchia di componenti.

Nota

Gli esempi di codice in questo articolo adottano tipi di riferimento nullable (NRT) e l'analisi statica dello stato null del compilatore .NET, supportati in ASP.NET Core in .NET 6 o versione successiva. Quando la destinazione è ASP.NET Core 5.0 o versioni precedenti, rimuovere la designazione di tipo Null (?) dai CascadingType?tipi , @ActiveTab?, RenderFragment?ITab?, TabSet?, e string? negli esempi dell'articolo.

Valori a cascata a livello radice

I valori a catena a livello radice possono essere registrati per l'intera gerarchia dei componenti. Sono supportati valori e sottoscrizioni denominati a catena per le notifiche di aggiornamento.

La classe seguente viene usata negli esempi di questa sezione.

Dalek.cs:

// "Dalek" ©Terry Nation https://www.imdb.com/name/nm0622334/
// "Doctor Who" ©BBC https://www.bbc.co.uk/programmes/b006q2x0

namespace BlazorSample;

public class Dalek
{
    public int Units { get; set; }
}
// "Dalek" ©Terry Nation https://www.imdb.com/name/nm0622334/
// "Doctor Who" ©BBC https://www.bbc.co.uk/programmes/b006q2x0

namespace BlazorSample;

public class Dalek
{
    public int Units { get; set; }
}

Le registrazioni seguenti vengono effettuate nel file dell'app Program con AddCascadingValue:

  • Dalek con un valore della proprietà per Units viene registrato come valore a catena fisso.
  • Una seconda Dalek registrazione con un valore di proprietà diverso per Units è denominata "AlphaGroup".
builder.Services.AddCascadingValue(sp => new Dalek { Units = 123 });
builder.Services.AddCascadingValue("AlphaGroup", sp => new Dalek { Units = 456 });

Nel componente seguente Daleks vengono visualizzati i valori a catena.

Daleks.razor:

@page "/daleks"

<PageTitle>Daleks</PageTitle>

<h1>Root-level Cascading Value Example</h1>

<ul>
    <li>Dalek Units: @Dalek?.Units</li>
    <li>Alpha Group Dalek Units: @AlphaGroupDalek?.Units</li>
</ul>

<p>
    Dalek© <a href="https://www.imdb.com/name/nm0622334/">Terry Nation</a><br>
    Doctor Who© <a href="https://www.bbc.co.uk/programmes/b006q2x0">BBC</a>
</p>

@code {
    [CascadingParameter]
    public Dalek? Dalek { get; set; }

    [CascadingParameter(Name = "AlphaGroup")]
    public Dalek? AlphaGroupDalek { get; set; }
}
@page "/daleks"

<PageTitle>Daleks</PageTitle>

<h1>Root-level Cascading Value Example</h1>

<ul>
    <li>Dalek Units: @Dalek?.Units</li>
    <li>Alpha Group Dalek Units: @AlphaGroupDalek?.Units</li>
</ul>

<p>
    Dalek© <a href="https://www.imdb.com/name/nm0622334/">Terry Nation</a><br>
    Doctor Who© <a href="https://www.bbc.co.uk/programmes/b006q2x0">BBC</a>
</p>

@code {
    [CascadingParameter]
    public Dalek? Dalek { get; set; }

    [CascadingParameter(Name = "AlphaGroup")]
    public Dalek? AlphaGroupDalek { get; set; }
}

Nell'esempio Dalek seguente viene registrato come valore a catena usando CascadingValueSource<T>, dove <T> è il tipo . Il isFixed flag indica se il valore è fisso. Se false, tutti i destinatari vengono sottoscritti per le notifiche di aggiornamento, che vengono rilasciate chiamando NotifyChangedAsync. Le sottoscrizioni creano overhead e riducono le prestazioni, quindi impostate su isFixed true se il valore non cambia.

builder.Services.AddCascadingValue(sp =>
{
    var dalek = new Dalek { Units = 789 };
    var source = new CascadingValueSource<Dalek>(dalek, isFixed: false);

    return source;
});

Avviso

La registrazione di un tipo di componente come valore a catena a livello radice non registra servizi aggiuntivi per il tipo o consente l'attivazione del servizio nel componente.

Gestire i servizi necessari separatamente dai valori a catena, registrandoli separatamente dal tipo a catena.

Evitare di usare AddCascadingValue per registrare un tipo di componente come valore a catena. Al contrario, eseguire il <Router>...</Router> wrapping di Routes nel componente (Components/Routes.razor) con il componente e adottare il rendering sul lato server interattivo globale (SSR interattivo). Per un esempio, vedere la sezione relativa al CascadingValue componente .

Componente CascadingValue

Un componente predecessore fornisce un valore a catena usando il componente del CascadingValue framework, che esegue il Blazor wrapping di un sottoalbero di una gerarchia di componenti e fornisce un singolo valore a tutti i componenti all'interno del relativo sottoalbero.

Nell'esempio seguente viene illustrato il flusso delle informazioni sul tema nella gerarchia dei componenti per fornire una classe di stile CSS ai pulsanti nei componenti figlio.

La classe C# seguente ThemeInfo specifica le informazioni sul tema.

Nota

Per gli esempi in questa sezione, lo spazio dei nomi dell'app è BlazorSample. Quando si sperimenta il codice nella propria app di esempio, modificare lo spazio dei nomi dell'app nello spazio dei nomi dell'app di esempio.

ThemeInfo.cs:

namespace BlazorSample;

public class ThemeInfo
{
    public string? ButtonClass { get; set; }
}
namespace BlazorSample;

public class ThemeInfo
{
    public string? ButtonClass { get; set; }
}
namespace BlazorSample.UIThemeClasses;

public class ThemeInfo
{
    public string? ButtonClass { get; set; }
}
namespace BlazorSample.UIThemeClasses;

public class ThemeInfo
{
    public string? ButtonClass { get; set; }
}
namespace BlazorSample.UIThemeClasses
{
    public class ThemeInfo
    {
        public string ButtonClass { get; set; }
    }
}
namespace BlazorSample.UIThemeClasses
{
    public class ThemeInfo
    {
        public string ButtonClass { get; set; }
    }
}

Il componente di layout seguente specifica le informazioni sul tema (ThemeInfo) come valore a catena per tutti i componenti che costituiscono il corpo del layout della Body proprietà. ButtonClass viene assegnato un valore di btn-success, che è uno stile pulsante Bootstrap. Qualsiasi componente discendente nella gerarchia dei componenti può usare la ButtonClass proprietà tramite il ThemeInfo valore a catena.

MainLayout.razor:

@inherits LayoutComponentBase

<div class="page">
    <div class="sidebar">
        <NavMenu />
    </div>

    <main>
        <div class="top-row px-4">
            <a href="https://video2.skills-academy.com/aspnet/core/" target="_blank">About</a>
        </div>

        <CascadingValue Value="theme">
            <article class="content px-4">
                @Body
            </article>
        </CascadingValue>
    </main>
</div>

<div id="blazor-error-ui" data-nosnippet>
    An unhandled error has occurred.
    <a href="." class="reload">Reload</a>
    <span class="dismiss">🗙</span>
</div>

@code {
    private ThemeInfo theme = new() { ButtonClass = "btn-success" };
}
@inherits LayoutComponentBase

<div class="page">
    <div class="sidebar">
        <NavMenu />
    </div>

    <main>
        <div class="top-row px-4">
            <a href="https://video2.skills-academy.com/aspnet/core/" target="_blank">About</a>
        </div>

        <CascadingValue Value="theme">
            <article class="content px-4">
                @Body
            </article>
        </CascadingValue>
    </main>
</div>

<div id="blazor-error-ui" data-nosnippet>
    An unhandled error has occurred.
    <a href="" class="reload">Reload</a>
    <a class="dismiss">🗙</a>
</div>

@code {
    private ThemeInfo theme = new() { ButtonClass = "btn-success" };
}
@inherits LayoutComponentBase
@using BlazorSample.UIThemeClasses

<div class="page">
    <div class="sidebar">
        <NavMenu />
    </div>

    <main>
        <div class="top-row px-4">
            <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
        </div>

        <CascadingValue Value="@theme">
            <article class="content px-4">
                @Body
            </article>
        </CascadingValue>
    </main>
</div>

@code {
    private ThemeInfo theme = new() { ButtonClass = "btn-success" };
}
@inherits LayoutComponentBase
@using BlazorSample.UIThemeClasses

<div class="page">
    <div class="sidebar">
        <NavMenu />
    </div>

    <main>
        <CascadingValue Value="@theme">
            <div class="content px-4">
                @Body
            </div>
        </CascadingValue>
    </main>
</div>

@code {
    private ThemeInfo theme = new() { ButtonClass = "btn-success" };
}
@inherits LayoutComponentBase
@using BlazorSample.UIThemeClasses

<div class="page">
    <div class="sidebar">
        <NavMenu />
    </div>

    <div class="main">
        <CascadingValue Value="@theme">
            <div class="content px-4">
                @Body
            </div>
        </CascadingValue>
    </div>
</div>

@code {
    private ThemeInfo theme = new() { ButtonClass = "btn-success" };
}
@inherits LayoutComponentBase
@using BlazorSample.UIThemeClasses

<div class="sidebar">
    <NavMenu />
</div>

<div class="main">
    <CascadingValue Value="theme">
        <div class="content px-4">
            @Body
        </div>
    </CascadingValue>
</div>

@code {
    private ThemeInfo theme = new ThemeInfo { ButtonClass = "btn-success" };
}

Blazor Web Apps forniscono approcci alternativi per i valori a catena che si applicano più ampiamente all'app rispetto all'arredamento tramite un unico file di layout:

  • Eseguire il wrapping del markup del Routes componente in un CascadingValue componente per specificare i dati come valore a catena per tutti i componenti dell'app.

    Nell'esempio seguente vengono propagati i ThemeInfo dati dal Routes componente .

    Routes.razor:

    <CascadingValue Value="theme">
        <Router ...>
            ...
        </Router>
    </CascadingValue>
    
    @code {
        private ThemeInfo theme = new() { ButtonClass = "btn-success" };
    }
    

    Nota

    Il wrapping dell'istanza Routes del App componente nel componente (Components/App.razor) con un CascadingValue componente non è supportato.

  • Specificare un valore a catena a livello radice come servizio chiamando il AddCascadingValue metodo di estensione nel generatore di raccolte di servizi.

    Nell'esempio seguente vengono propagati i ThemeInfo dati dal Program file .

    Program.cs

    builder.Services.AddCascadingValue(sp => 
        new ThemeInfo() { ButtonClass = "btn-primary" });
    

Per altre informazioni, vedere le sezioni seguenti di questo articolo:

Attributo [CascadingParameter]

Per usare i valori a catena, i componenti discendenti dichiarano parametri a catena usando l'attributo [CascadingParameter]. I valori a catena sono associati a parametri a catena per tipo. La propagazione di più valori dello stesso tipo è descritta nella sezione Cascade multiple values più avanti in questo articolo.

Il componente seguente associa il ThemeInfo valore a catena a un parametro a catena, facoltativamente usando lo stesso nome di ThemeInfo. Il parametro viene usato per impostare la classe CSS per il Increment Counter (Themed) pulsante.

ThemedCounter.razor:

@page "/themed-counter"

<PageTitle>Themed Counter</PageTitle>

<h1>Themed Counter Example</h1>

<p>Current count: @currentCount</p>

<p>
    <button @onclick="IncrementCount">
        Increment Counter (Unthemed)
    </button>
</p>

<p>
    <button 
        class="btn @(ThemeInfo is not null ? ThemeInfo.ButtonClass : string.Empty)" 
        @onclick="IncrementCount">
        Increment Counter (Themed)
    </button>
</p>

@code {
    private int currentCount = 0;

    [CascadingParameter]
    protected ThemeInfo? ThemeInfo { get; set; }

    private void IncrementCount() => currentCount++;
}
@page "/themed-counter"

<PageTitle>Themed Counter</PageTitle>

<h1>Themed Counter Example</h1>

<p>Current count: @currentCount</p>

<p>
    <button @onclick="IncrementCount">
        Increment Counter (Unthemed)
    </button>
</p>

<p>
    <button 
        class="btn @(ThemeInfo is not null ? ThemeInfo.ButtonClass : string.Empty)" 
        @onclick="IncrementCount">
        Increment Counter (Themed)
    </button>
</p>

@code {
    private int currentCount = 0;

    [CascadingParameter]
    protected ThemeInfo? ThemeInfo { get; set; }

    private void IncrementCount() => currentCount++;
}
@page "/themed-counter"
@using BlazorSample.UIThemeClasses

<h1>Themed Counter</h1>

<p>Current count: @currentCount</p>

<p>
    <button @onclick="IncrementCount">
        Increment Counter (Unthemed)
    </button>
</p>

<p>
    <button 
        class="btn @(ThemeInfo is not null ? ThemeInfo.ButtonClass : string.Empty)" 
        @onclick="IncrementCount">
        Increment Counter (Themed)
    </button>
</p>

@code {
    private int currentCount = 0;

    [CascadingParameter]
    protected ThemeInfo? ThemeInfo { get; set; }

    private void IncrementCount()
    {
        currentCount++;
    }
}
@page "/themed-counter"
@using BlazorSample.UIThemeClasses

<h1>Themed Counter</h1>

<p>Current count: @currentCount</p>

<p>
    <button @onclick="IncrementCount">
        Increment Counter (Unthemed)
    </button>
</p>

<p>
    <button 
        class="btn @(ThemeInfo is not null ? ThemeInfo.ButtonClass : string.Empty)" 
        @onclick="IncrementCount">
        Increment Counter (Themed)
    </button>
</p>

@code {
    private int currentCount = 0;

    [CascadingParameter]
    protected ThemeInfo? ThemeInfo { get; set; }

    private void IncrementCount()
    {
        currentCount++;
    }
}
@page "/themed-counter"
@using BlazorSample.UIThemeClasses

<h1>Themed Counter</h1>

<p>Current count: @currentCount</p>

<p>
    <button @onclick="IncrementCount">
        Increment Counter (Unthemed)
    </button>
</p>

<p>
    <button class="btn @ThemeInfo.ButtonClass" @onclick="IncrementCount">
        Increment Counter (Themed)
    </button>
</p>

@code {
    private int currentCount = 0;

    [CascadingParameter]
    protected ThemeInfo ThemeInfo { get; set; }

    private void IncrementCount()
    {
        currentCount++;
    }
}
@page "/themed-counter"
@using BlazorSample.UIThemeClasses

<h1>Themed Counter</h1>

<p>Current count: @currentCount</p>

<p>
    <button @onclick="IncrementCount">
        Increment Counter (Unthemed)
    </button>
</p>

<p>
    <button class="btn @ThemeInfo.ButtonClass" @onclick="IncrementCount">
        Increment Counter (Themed)
    </button>
</p>

@code {
    private int currentCount = 0;

    [CascadingParameter]
    protected ThemeInfo ThemeInfo { get; set; }

    private void IncrementCount()
    {
        currentCount++;
    }
}

Analogamente a un parametro di componente normale, i componenti che accettano un parametro a catena vengono rirenderati quando viene modificato il valore a catena. Ad esempio, la configurazione di un'istanza del tema diversa causa il ripristino del ThemedCounter componente dalla sezione del CascadingValue componente .

MainLayout.razor:

<main>
    <div class="top-row px-4">
        <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
    </div>

    <CascadingValue Value="theme">
        <article class="content px-4">
            @Body
        </article>
    </CascadingValue>
    <button @onclick="ChangeToDarkTheme">Dark mode</button>
</main>

@code {
    private ThemeInfo theme = new() { ButtonClass = "btn-success" };

    private void ChangeToDarkTheme()
    {
        theme = new() { ButtonClass = "btn-secondary" };
    }
}

CascadingValue<TValue>.IsFixed può essere usato per indicare che un parametro a catena non cambia dopo l'inizializzazione.

Valori/parametri a catena e limiti della modalità di rendering

I parametri a catena non passano i dati tra i limiti della modalità di rendering:

  • Le sessioni interattive vengono eseguite in un contesto diverso rispetto alle pagine che usano il rendering statico lato server (SSR statico). Non è necessario che il server che produce la pagina sia anche lo stesso computer che ospita alcune sessioni successive di Interactive Server, incluso per i componenti WebAssembly in cui il server è un computer diverso per il client. Il vantaggio del rendering statico lato server (SSR statico) è quello di ottenere le prestazioni complete del rendering HTML senza stato puro.

  • Lo stato che supera il limite tra il rendering statico e interattivo deve essere serializzabile. I componenti sono oggetti arbitrari che fanno riferimento a una vasta catena di altri oggetti, tra cui il renderer, il contenitore DI e ogni istanza del servizio di inserimento delle dipendenze. È necessario fare in modo esplicito che lo stato venga serializzato da SSR statico per renderlo disponibile nei componenti visualizzati in modo interattivo successivo. Vengono adottati due approcci:

    • Tramite il Blazor framework, i parametri passati attraverso un ssrio statico al limite di rendering interattivo vengono serializzati automaticamente se sono serializzabili in JSON o viene generato un errore.
    • Lo stato archiviato in PersistentComponentState viene serializzato e ripristinato automaticamente se è serializzabile in JSON o viene generato un errore.

I parametri a catena non sono serializzabili in JSON perché i modelli di utilizzo tipici per i parametri a catena sono in qualche modo simili ai servizi di inserimento delle dipendenze. Spesso ci sono varianti specifiche della piattaforma di parametri a catena, quindi sarebbe inutili per gli sviluppatori se il framework impedisse agli sviluppatori di avere versioni specifiche del server o versioni specifiche di WebAssembly. Inoltre, molti valori di parametro a catena in generale non sono serializzabili, quindi sarebbe poco pratico aggiornare le app esistenti se si dovesse interrompere l'uso di tutti i valori dei parametri nonerializzabili a catena.

Raccomandazioni:

  • Se è necessario rendere disponibile lo stato per tutti i componenti interattivi come parametro a catena, è consigliabile usare valori a catena a livello radice. È disponibile un modello factory e l'app può generare valori aggiornati dopo l'avvio dell'app. I valori a catena a livello radice sono disponibili per tutti i componenti, inclusi i componenti interattivi, poiché vengono elaborati come servizi di inserimento delle dipendenze.

  • Per gli autori di librerie di componenti, è possibile creare un metodo di estensione per i consumer di libreria in modo simile al seguente:

    builder.Services.AddLibraryCascadingParameters();
    

    Indicare agli sviluppatori di chiamare il metodo di estensione. Si tratta di un'alternativa audio per indicare loro di aggiungere un <RootComponent> componente nel componente MainLayout .

Più valori a catena

Per propagare più valori dello stesso tipo all'interno dello stesso sottoalbero, specificare una stringa univoca Name per ogni CascadingValue componente e i relativi attributi.[CascadingParameter]

Nell'esempio seguente due CascadingValue componenti si sovrapporno a istanze diverse di CascadingType:

<CascadingValue Value="parentCascadeParameter1" Name="CascadeParam1">
    <CascadingValue Value="ParentCascadeParameter2" Name="CascadeParam2">
        ...
    </CascadingValue>
</CascadingValue>

@code {
    private CascadingType? parentCascadeParameter1;

    [Parameter]
    public CascadingType? ParentCascadeParameter2 { get; set; }
}

In un componente discendente, i parametri a catena ricevono i valori a catena dal componente predecessore da Name:

@code {
    [CascadingParameter(Name = "CascadeParam1")]
    protected CascadingType? ChildCascadeParameter1 { get; set; }

    [CascadingParameter(Name = "CascadeParam2")]
    protected CascadingType? ChildCascadeParameter2 { get; set; }
}

Passare i dati attraverso una gerarchia di componenti

I parametri a catena consentono anche ai componenti di passare i dati in una gerarchia di componenti. Si consideri l'esempio di set di schede dell'interfaccia utente seguente, in cui un componente del set di schede gestisce una serie di singole schede.

Nota

Per gli esempi in questa sezione, lo spazio dei nomi dell'app è BlazorSample. Quando si sperimenta il codice nella propria app di esempio, modificare lo spazio dei nomi dell'app di esempio nello spazio dei nomi dell'app di esempio.

Creare un'interfaccia ITab implementata da schede in una cartella denominata UIInterfaces.

UIInterfaces/ITab.cs:

using Microsoft.AspNetCore.Components;

namespace BlazorSample.UIInterfaces;

public interface ITab
{
    RenderFragment ChildContent { get; }
}

Nota

Per altre informazioni su RenderFragment, vedere ASP.NET Componenti di baseRazor.

Il componente seguente TabSet gestisce un set di schede. I componenti del set di Tab schede, creati più avanti in questa sezione, specificano gli elementi di elenco () per l'elenco (<li>...</li><ul>...</ul>).

I componenti figlio Tab non vengono passati in modo esplicito come parametri a TabSet. I componenti figlio Tab fanno invece parte del contenuto figlio di TabSet. Tuttavia, è TabSet comunque necessario un riferimento a ogni Tab componente in modo che possa eseguire il rendering delle intestazioni e della scheda attiva. Per abilitare questo coordinamento senza richiedere codice aggiuntivo, il TabSet componente può fornire se stesso come valore a catena che viene quindi prelevato dai componenti discendenti Tab .

TabSet.razor:

@using BlazorSample.UIInterfaces

<!-- Display the tab headers -->

<CascadingValue Value="this">
    <ul class="nav nav-tabs">
        @ChildContent
    </ul>
</CascadingValue>

<!-- Display body for only the active tab -->

<div class="nav-tabs-body p-4">
    @ActiveTab?.ChildContent
</div>

@code {
    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    public ITab? ActiveTab { get; private set; }

    public void AddTab(ITab tab)
    {
        if (ActiveTab is null)
        {
            SetActiveTab(tab);
        }
    }

    public void SetActiveTab(ITab tab)
    {
        if (ActiveTab != tab)
        {
            ActiveTab = tab;
            StateHasChanged();
        }
    }
}

I componenti discendenti Tab acquisisce l'oggetto che lo contiene TabSet come parametro a catena. I Tab componenti si aggiungono alla TabSet coordinata e per impostare la scheda attiva.

Tab.razor:

@using BlazorSample.UIInterfaces
@implements ITab

<li>
    <a @onclick="ActivateTab" class="nav-link @TitleCssClass" role="button">
        @Title
    </a>
</li>

@code {
    [CascadingParameter]
    public TabSet? ContainerTabSet { get; set; }

    [Parameter]
    public string? Title { get; set; }

    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    private string? TitleCssClass => 
        ContainerTabSet?.ActiveTab == this ? "active" : null;

    protected override void OnInitialized()
    {
        ContainerTabSet?.AddTab(this);
    }

    private void ActivateTab()
    {
        ContainerTabSet?.SetActiveTab(this);
    }
}

Il componente seguente ExampleTabSet usa il TabSet componente , che contiene tre Tab componenti.

ExampleTabSet.razor:

@page "/example-tab-set"

<TabSet>
    <Tab Title="First tab">
        <h4>Greetings from the first tab!</h4>

        <label>
            <input type="checkbox" @bind="showThirdTab" />
            Toggle third tab
        </label>
    </Tab>

    <Tab Title="Second tab">
        <h4>Hello from the second tab!</h4>
    </Tab>

    @if (showThirdTab)
    {
        <Tab Title="Third tab">
            <h4>Welcome to the disappearing third tab!</h4>
            <p>Toggle this tab from the first tab.</p>
        </Tab>
    }
</TabSet>

@code {
    private bool showThirdTab;
}

Risorse aggiuntive