Eseguire la migrazione da Web Forms ASP.NET a Blazor

Suggerimento

Questo contenuto è un estratto dell'eBook, Blazor per gli sviluppatori di Web Forms ASP.NET per Azure, disponibile in .NET Docs o come PDF scaricabile gratuitamente che può essere letto offline.

Blazor-for-ASP-NET-Web-Forms-Developers eBook cover thumbnail.

La migrazione di una codebase da Web Forms ASP.NET a Blazor è un'attività dispendiosa in termini di tempo e che va pianificata. Questo capitolo ne descrive il processo. Per facilitare la transizione, assicurarsi che l'app sia conforme a un'architettura a più livelli, in cui il modello di app (in questo caso, Web Forms) è separato dalla logica di business. Questa separazione logica dei livelli rende chiaro cosa deve essere spostato in .NET Core e Blazor.

Questo esempio usa l'app eShop disponibile in GitHub. eShop è un servizio di catalogo che fornisce funzionalità CRUD tramite immissione e convalida del modulo.

Perché eseguire la migrazione di un'app funzionante a Blazor? Spesso, non c'è bisogno. Web Forms ASP.NET Form continuerà a essere supportato per molti anni. Tuttavia, molte delle funzionalità fornite da Blazor sono supportate solo in un'app di cui è stata eseguita la migrazione. Tali funzionalità includono:

  • Miglioramenti delle prestazioni nel framework, ad esempio Span<T>
  • Possibilità di essere eseguito come WebAssembly
  • Supporto multipiattaforma per Linux e macOS
  • Distribuzione locale dell'app o distribuzione del framework condiviso senza influire su altre app

Se si è interessati a queste o altre nuove funzionalità, è consigliabile eseguire la migrazione dell'app. La migrazione può essere eseguita in diversi modi: sull’intera app o solo su determinati endpoint che richiedono modifiche. In definitiva, la decisione di eseguire la migrazione è basata sui problemi aziendali che lo sviluppatore deve risolvere.

Hosting lato del server vs. lato del client

Come illustrato nel capitolo sui modelli di hosting, un'app Blazor può essere ospitata in due modi: dal lato del server e da quello del client. Il modello del lato del server usa connessioni SignalR ASP.NET Core per gestire gli aggiornamenti DOM durante l'esecuzione di qualsiasi codice effettivo nel server. Il modello del lato del client viene eseguito come WebAssembly all'interno di un browser e non richiede connessioni a server. Esistono varie differenze che potrebbero determinare quale sia la scelta più indicata per una specifica app:

  • Al momento, l'esecuzione come WebAssembly non supporta tutte le funzionalità (ad esempio, il threading)
  • Frequente comunicazione tra client e server può causare problemi di latenza in modalità del lato del server
  • L'accesso a database e a servizi interni o protetti richiede un servizio separato con hosting del lato del client

Durante il processo di scrittura, il modello del lato del server è più simile a Web Forms. La maggior parte di questo capitolo è incentrata sul modello di hosting del lato del server, in quanto pronto per la produzione.

Crea un nuovo progetto

Il passaggio iniziale della migrazione consiste nel creare un nuovo progetto. Questo tipo di progetto si basa su progetti di stile SDK di .NET e semplifica gran parte del boilerplate usato nei formati di progetto precedenti. Per ulteriori dettagli, consultare il capitolo sulla struttura del progetto.

Dopo aver creato il progetto, installare le librerie usate nel progetto precedente. In progetti Web Forms meno recenti, potrebbe essere stato usato il file packages.config per elencare i pacchetti NuGet necessari. Nel nuovo progetto in stile SDK packages.con, è stato sostituito con elementi <PackageReference> nel file di progetto. Un vantaggio di questo approccio è che tutte le dipendenze sono installate in modo transitivo. È possibile elencare solo le dipendenze di primo livello a cui si è interessati.

Molte delle dipendenze in uso, tra cui Entity Framework 6 e log4net, sono disponibili per .NET. Se non è disponibile alcuna versione di .NET o .NET Standard, è spesso possibile usare la versione .NET Framework. Il chilometraggio può variare. L’uso di qualsiasi API non disponibile in .NET causerà un errore di runtime. Visual Studio notifica l'utente di tali pacchetti. Comparirà un'icona gialla nel nodo Riferimenti del progetto in Esplora soluzioni.

Nel progetto eShop basato su Blazor, è possibile visualizzare i pacchetti installati. In precedenza, il file packages.config elencava ogni pacchetto usato nel progetto, generando un file lungo quasi 50 righe. Un frammento di packages.config è:

<?xml version="1.0" encoding="utf-8"?>
<packages>
  ...
  <package id="Microsoft.ApplicationInsights.Agent.Intercept" version="2.4.0" targetFramework="net472" />
  <package id="Microsoft.ApplicationInsights.DependencyCollector" version="2.9.1" targetFramework="net472" />
  <package id="Microsoft.ApplicationInsights.PerfCounterCollector" version="2.9.1" targetFramework="net472" />
  <package id="Microsoft.ApplicationInsights.Web" version="2.9.1" targetFramework="net472" />
  <package id="Microsoft.ApplicationInsights.WindowsServer" version="2.9.1" targetFramework="net472" />
  <package id="Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel" version="2.9.1" targetFramework="net472" />
  <package id="Microsoft.AspNet.FriendlyUrls" version="1.0.2" targetFramework="net472" />
  <package id="Microsoft.AspNet.FriendlyUrls.Core" version="1.0.2" targetFramework="net472" />
  <package id="Microsoft.AspNet.ScriptManager.MSAjax" version="5.0.0" targetFramework="net472" />
  <package id="Microsoft.AspNet.ScriptManager.WebForms" version="5.0.0" targetFramework="net472" />
  ...
  <package id="System.Memory" version="4.5.1" targetFramework="net472" />
  <package id="System.Numerics.Vectors" version="4.4.0" targetFramework="net472" />
  <package id="System.Runtime.CompilerServices.Unsafe" version="4.5.0" targetFramework="net472" />
  <package id="System.Threading.Channels" version="4.5.0" targetFramework="net472" />
  <package id="System.Threading.Tasks.Extensions" version="4.5.1" targetFramework="net472" />
  <package id="WebGrease" version="1.6.0" targetFramework="net472" />
</packages>

L'elemento <packages> include tutte le dipendenze necessarie. È difficile identificare quali di questi pacchetti sono inclusi, poiché sono necessari. Alcuni elementi <package> sono inclusi nell’elenco semplicemente per soddisfare le esigenze delle dipendenze necessarie.

Il progetto Blazor elenca le dipendenze necessarie all'interno di un elemento <ItemGroup> nel file di progetto:

<ItemGroup>
    <PackageReference Include="Autofac" Version="4.9.3" />
    <PackageReference Include="EntityFramework" Version="6.4.4" />
    <PackageReference Include="log4net" Version="2.0.12" />
    <PackageReference Include="Microsoft.Extensions.Logging.Log4Net.AspNetCore" Version="2.2.12" />
</ItemGroup>

Un pacchetto NuGet che semplifica la vita degli sviluppatori di Web Forms Windows Compatibility Pack. Anche se .NET è multipiattaforma, alcune funzionalità sono disponibili solo in Windows. Le funzionalità specifiche a Windows vengono rese disponibili installando il pacchetto di compatibilità. Tali funzionalità includono Registro, WMI e Servizio directory. Il pacchetto aggiunge circa 20.000 API e attiva molti servizi con cui si potrebbe già avere familiarità. Il progetto eShop non richiede il pacchetto di compatibilità, ma se i progetti usano funzionalità specifiche di Windows, il pacchetto semplifica le attività di migrazione.

Abilitare il processo di avvio

Il processo di avvio per Blazor è diverso da quello per Web Forms e segue una configurazione simile per altri servizi ASP.NET Core. Quando ospitati sul lato del server, i componenti Razor vengono eseguiti come parte di una normale app ASP.NET Core. Se ospitati nel browser con WebAssembly, i componenti Razor usano un modello di hosting simile. La differenza è che i componenti vengono eseguiti come servizio separato da uno dei processi back-end. In entrambi i casi, l'avvio è simile.

Il file Global.asax.cs è la pagina di avvio predefinita per progetti Web Forms. Nel progetto eShop, questo file configura il contenitore Inversion of Control (IoC) e gestisce i vari eventi del ciclo di vita dell'app o della richiesta. Alcuni di questi eventi sono gestiti con middleware, ad esempio Application_BeginRequest. Altri eventi richiedono l'override di servizi specifici tramite l'inserimento delle dipendenze (DI).

Ad esempio, il file Global.asax.cs per eShop contiene il seguente codice:

public class Global : HttpApplication, IContainerProviderAccessor
{
    private static readonly ILog _log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

    static IContainerProvider _containerProvider;
    IContainer container;

    public IContainerProvider ContainerProvider
    {
        get { return _containerProvider; }
    }

    protected void Application_Start(object sender, EventArgs e)
    {
        // Code that runs on app startup
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
        ConfigureContainer();
        ConfigDataBase();
    }

    /// <summary>
    /// Track the machine name and the start time for the session inside the current session
    /// </summary>
    protected void Session_Start(Object sender, EventArgs e)
    {
        HttpContext.Current.Session["MachineName"] = Environment.MachineName;
        HttpContext.Current.Session["SessionStartTime"] = DateTime.Now;
    }

    /// <summary>
    /// https://autofaccn.readthedocs.io/en/latest/integration/webforms.html
    /// </summary>
    private void ConfigureContainer()
    {
        var builder = new ContainerBuilder();
        var mockData = bool.Parse(ConfigurationManager.AppSettings["UseMockData"]);
        builder.RegisterModule(new ApplicationModule(mockData));
        container = builder.Build();
        _containerProvider = new ContainerProvider(container);
    }

    private void ConfigDataBase()
    {
        var mockData = bool.Parse(ConfigurationManager.AppSettings["UseMockData"]);

        if (!mockData)
        {
            Database.SetInitializer<CatalogDBContext>(container.Resolve<CatalogDBInitializer>());
        }
    }

    protected void Application_BeginRequest(object sender, EventArgs e)
    {
        //set the property to our new object
        LogicalThreadContext.Properties["activityid"] = new ActivityIdHelper();

        LogicalThreadContext.Properties["requestinfo"] = new WebRequestInfo();

        _log.Debug("Application_BeginRequest");
    }
}

Il file precedente diventa il file Program.cs sul lato del server Blazor:

using eShopOnBlazor.Models;
using eShopOnBlazor.Models.Infrastructure;
using eShopOnBlazor.Services;
using log4net;
using System.Data.Entity;
using eShopOnBlazor;

var builder = WebApplication.CreateBuilder(args);

// add services

builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();

if (builder.Configuration.GetValue<bool>("UseMockData"))
{
    builder.Services.AddSingleton<ICatalogService, CatalogServiceMock>();
}
else
{
    builder.Services.AddScoped<ICatalogService, CatalogService>();
    builder.Services.AddScoped<IDatabaseInitializer<CatalogDBContext>, CatalogDBInitializer>();
    builder.Services.AddSingleton<CatalogItemHiLoGenerator>();
    builder.Services.AddScoped(_ => new CatalogDBContext(builder.Configuration.GetConnectionString("CatalogDBContext")));
}

var app = builder.Build();

new LoggerFactory().AddLog4Net("log4Net.xml");

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler("/Home/Error");
}

// Middleware for Application_BeginRequest
app.Use((ctx, next) =>
{
    LogicalThreadContext.Properties["activityid"] = new ActivityIdHelper(ctx);
    LogicalThreadContext.Properties["requestinfo"] = new WebRequestInfo(ctx);
    return next();
});

app.UseStaticFiles();

app.UseRouting();

app.UseEndpoints(endpoints =>
{
    endpoints.MapBlazorHub();
    endpoints.MapFallbackToPage("/_Host");
});

ConfigDataBase(app);

void ConfigDataBase(IApplicationBuilder app)
{
    using (var scope = app.ApplicationServices.CreateScope())
    {
        var initializer = scope.ServiceProvider.GetService<IDatabaseInitializer<CatalogDBContext>>();

        if (initializer != null)
        {
            Database.SetInitializer(initializer);
        }
    }
}

app.Run();

Una modifica significativa rispetto a Web Forms è l’importanza dell'inserimento delle dipendenze (DI). L'inserimento delle dipendenze è stato un principio guida nella progettazione ASP.NET Core. Supporta la personalizzazione di quasi tutti gli aspetti del framework ASP.NET Core. Ha anche un provider di servizi integrato che può essere usato in molti scenari. Se è necessaria una maggiore personalizzazione, può essere supportata da molti progetti della community. Ad esempio, è possibile portare avanti l'investimento della libreria DI di terze parti.

Nell'app eShop originale, è disponibile una configurazione per la gestione di sessioni. Poiché il lato del server Blazor usa ASP.NET Core SignalR per la comunicazione, lo stato sessione non è supportato perché le connessioni possono verificarsi indipendentemente da un contesto HTTP. Un'app che usa lo stato sessione richiede la riprogettazione prima dell'esecuzione come app Blazor.

Per ulteriori informazioni sull'avvio dell'app, consultare Avvio dell’app.

Eseguire la migrazione di moduli e gestori HTTP al middleware

Moduli e gestori HTTP sono modelli comuni in Web Forms per controllare la pipeline di richieste HTTP. Le classi che implementano IHttpModule o IHttpHandler possono essere registrate ed elaborano le richieste in ingresso. Web Forms configura moduli e gestori nel file web.config. Web Forms è anchE ampiamente basato sulla gestione degli eventi del ciclo di vita delle app. ASP.NET Core, invece, usa il middleware. Il middleware viene registrato nel metodo Configure della classe Startup. L'ordine di esecuzione del middleware è determinato dall'ordine di registrazione.

Nella sezione Abilita processo di avvio, un evento del ciclo di vita è stato generato da Web Forms come metodo Application_BeginRequest. Questo evento non è disponibile in ASP.NET Core. Un modo per ottenere questo comportamento consiste nell'implementare il middleware come illustrato nell'esempio del file Startup.cs. Questo middleware segue la stessa logica e quindi trasferisce il controllo al gestore successivo nella pipeline middleware.

Per ulteriori informazioni sulla migrazione di moduli e gestori, consultare Eseguire la migrazione di gestori e moduli HTTP a middleware ASP.NET Core.

Eseguire la migrazione di file statici

Per gestire file statici (come HTML, CSS, immagini e JavaScript), i file devono essere esposti dal middleware. La chiamata del metodo UseStaticFiles abilita la gestione di file statici dal percorso radice Web. La directory radice Web predefinita è wwwroot, ma può essere personalizzata. Come incluso nel file Program.cs :

...

app.UseStaticFiles();

...

Il progetto eShop consente l'accesso ai file statici di base. Sono disponibili molte personalizzazioni per l'accesso ai file statici. Per informazioni sull'abilitazione dei file predefiniti o di un browser di file, consultare File statici in ASP.NET Core.

Eseguire la migrazione del runtime della creazione di bundle e del setup di minimizzazione

La creazione di bundle e la minimizzazione sono tecniche di ottimizzazione delle prestazioni per ridurre il numero e le dimensioni delle richieste del server di recuperare determinati tipi di file. Spesso, JavaScript e CSS subiscono una qualche forma di creazione di bundle o minimizzazione prima di essere inviati al client. In Web Forms ASP.NET, queste ottimizzazioni vengono gestite in fase di esecuzione. Le convenzioni di ottimizzazione sono definite in un file App_Start/BundleConfig.cs. ASP.NET Core adotta un approccio più dichiarativo. Un file elenca i file da minimizzare, insieme a specifiche impostazioni di minimizzazione.

Per ulteriori informazioni sulla creazione di bundle e la minimizzazione, consultare Creare bundle e minimizzare asset statici in ASP.NET Core.

Eseguire la migrazione delle pagine ASPX

Una pagina in un'app Web Forms è un file con l'estensione .aspx. È spesso possibile eseguire il mapping di una pagina Web Forms a un componente in Blazor. Un componente Razor viene creato in un file con estensione .razor. Per il progetto eShop, cinque pagine vengono convertite in una pagina Razor.

Ad esempio, la visualizzazione dettagliata include tre file nel progetto Web Forms: Details.aspx, Details.aspx.cse Details.aspx.designer.cs. Quando si esegue la conversione a Blazor, il code-behind e il markup vengono combinati in Details.razor. La compilazione Razor (equivalente al contenuto dei file .designer.cs) viene archiviata nella directory obje non è, per impostazione predefinita, visualizzabile in Esplora soluzioni. La pagina Web Forms è costituita dal seguente markup:

<%@ Page Title="Details" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="Details.aspx.cs" Inherits="eShopLegacyWebForms.Catalog.Details" %>

<asp:Content ID="Details" ContentPlaceHolderID="MainContent" runat="server">
    <h2 class="esh-body-title">Details</h2>

    <div class="container">
        <div class="row">
            <asp:Image runat="server" CssClass="col-md-6 esh-picture" ImageUrl='<%#"/Pics/" + product.PictureFileName%>' />
            <dl class="col-md-6 dl-horizontal">
                <dt>Name
                </dt>

                <dd>
                    <asp:Label runat="server" Text='<%#product.Name%>' />
                </dd>

                <dt>Description
                </dt>

                <dd>
                    <asp:Label runat="server" Text='<%#product.Description%>' />
                </dd>

                <dt>Brand
                </dt>

                <dd>
                    <asp:Label runat="server" Text='<%#product.CatalogBrand.Brand%>' />
                </dd>

                <dt>Type
                </dt>

                <dd>
                    <asp:Label runat="server" Text='<%#product.CatalogType.Type%>' />
                </dd>
                <dt>Price
                </dt>

                <dd>
                    <asp:Label CssClass="esh-price" runat="server" Text='<%#product.Price%>' />
                </dd>

                <dt>Picture name
                </dt>

                <dd>
                    <asp:Label runat="server" Text='<%#product.PictureFileName%>' />
                </dd>

                <dt>Stock
                </dt>

                <dd>
                    <asp:Label runat="server" Text='<%#product.AvailableStock%>' />
                </dd>

                <dt>Restock
                </dt>

                <dd>
                    <asp:Label runat="server" Text='<%#product.RestockThreshold%>' />
                </dd>

                <dt>Max stock
                </dt>

                <dd>
                    <asp:Label runat="server" Text='<%#product.MaxStockThreshold%>' />
                </dd>

            </dl>
        </div>

        <div class="form-actions no-color esh-link-list">
            <a runat="server" href='<%# GetRouteUrl("EditProductRoute", new {id =product.Id}) %>' class="esh-link-item">Edit
            </a>
            |
            <a runat="server" href="~" class="esh-link-item">Back to list
            </a>
        </div>

    </div>
</asp:Content>

Il code-behind del markup precedente include il seguente codice:

using eShopLegacyWebForms.Models;
using eShopLegacyWebForms.Services;
using log4net;
using System;
using System.Web.UI;

namespace eShopLegacyWebForms.Catalog
{
    public partial class Details : System.Web.UI.Page
    {
        private static readonly ILog _log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        protected CatalogItem product;

        public ICatalogService CatalogService { get; set; }

        protected void Page_Load(object sender, EventArgs e)
        {
            var productId = Convert.ToInt32(Page.RouteData.Values["id"]);
            _log.Info($"Now loading... /Catalog/Details.aspx?id={productId}");
            product = CatalogService.FindCatalogItem(productId);

            this.DataBind();
        }
    }
}

Se convertita a Blazor, la pagina Web Forms viene convertita nel seguente codice:

@page "/Catalog/Details/{id:int}"
@inject ICatalogService CatalogService
@inject ILogger<Details> Logger

<h2 class="esh-body-title">Details</h2>

<div class="container">
    <div class="row">
        <img class="col-md-6 esh-picture" src="@($"/Pics/{_item.PictureFileName}")">

        <dl class="col-md-6 dl-horizontal">
            <dt>
                Name
            </dt>

            <dd>
                @_item.Name
            </dd>

            <dt>
                Description
            </dt>

            <dd>
                @_item.Description
            </dd>

            <dt>
                Brand
            </dt>

            <dd>
                @_item.CatalogBrand.Brand
            </dd>

            <dt>
                Type
            </dt>

            <dd>
                @_item.CatalogType.Type
            </dd>
            <dt>
                Price
            </dt>

            <dd>
                @_item.Price
            </dd>

            <dt>
                Picture name
            </dt>

            <dd>
                @_item.PictureFileName
            </dd>

            <dt>
                Stock
            </dt>

            <dd>
                @_item.AvailableStock
            </dd>

            <dt>
                Restock
            </dt>

            <dd>
                @_item.RestockThreshold
            </dd>

            <dt>
                Max stock
            </dt>

            <dd>
                @_item.MaxStockThreshold
            </dd>

        </dl>
    </div>

    <div class="form-actions no-color esh-link-list">
        <a href="@($"/Catalog/Edit/{_item.Id}")" class="esh-link-item">
            Edit
        </a>
        |
        <a href="/" class="esh-link-item">
            Back to list
        </a>
    </div>

</div>

@code {
    private CatalogItem _item;

    [Parameter]
    public int Id { get; set; }

    protected override void OnInitialized()
    {
        Logger.LogInformation("Now loading... /Catalog/Details/{Id}", Id);

        _item = CatalogService.FindCatalogItem(Id);
    }
}

Notare che il codice e il markup si trovano nello stesso file. Tutti i servizi necessari sono resi accessibili con l'attributo @inject. In base alla direttiva @page, è possibile accedere a questa pagina alla route Catalog/Details/{id}. Il valore del segnaposto della route {id} è stato limitato a un numero intero. Come descritto nella sezione sul routing, a differenza di Web Forms, un componente Razor indica in modo esplicito la route e tutti i parametri inclusi. Molti controlli Web Forms potrebbero non avere controparti esatte in Blazor. Spesso esiste un frammento HTML equivalente che può svolgere lo stesso ruolo. Ad esempio, il controllo <asp:Label /> può essere sostituito con un elemento HTML <label>.

Convalida del modello in Blazor

Se il codice Web Forms include la convalida, è possibile trasferire gran parte del carico con modifiche minime o nulle. Un vantaggio dell'esecuzione in Blazor è che la stessa logica di convalida può essere eseguita senza bisogno di JavaScript personalizzato. Le annotazioni dei dati consentono una semplice convalida del modello.

Ad esempio, la pagina Create.aspx include un modulo di immissione dati con convalida. Un frammento di codice di esempio sarà simile al seguente:

<div class="form-group">
    <label class="control-label col-md-2">Name</label>
    <div class="col-md-3">
        <asp:TextBox ID="Name" runat="server" CssClass="form-control"></asp:TextBox>
        <asp:RequiredFieldValidator runat="server" ControlToValidate="Name" Display="Dynamic"
            CssClass="field-validation-valid text-danger" ErrorMessage="The Name field is required." />
    </div>
</div>

In Blazor, il markup equivalente viene fornito in un file Create.razor:

<EditForm Model="_item" OnValidSubmit="@...">
    <DataAnnotationsValidator />

    <div class="form-group">
        <label class="control-label col-md-2">Name</label>
        <div class="col-md-3">
            <InputText class="form-control" @bind-Value="_item.Name" />
            <ValidationMessage For="(() => _item.Name)" />
        </div>
    </div>

    ...
</EditForm>

Il contesto EditForm include il supporto della convalida e può essere sottoposto a wrapping intorno a un input. Le annotazioni dei dati sono un metodo comune per aggiungere la convalida. Tale supporto di convalida può essere aggiunto tramite il componente DataAnnotationsValidator. Per ulteriori informazioni su questo meccanismo, consultare moduli e convalida ASP.NET Core.Blazor.

Eseguire la migrazione della configurazione

In un progetto Web Forms, i dati di configurazione vengono generalmente archiviati nel file web.config. È possibile accedere ai dati di configurazione tramite ConfigurationManager. Servizi erano spesso necessari per analizzare oggetti. Con .NET Framework 4.7.2, la componibilità è stata aggiunta alla configurazione tramite ConfigurationBuilders. Questi generatori hanno consentito agli sviluppatori di aggiungere varie origini per la configurazione che è stata quindi composta in fase di esecuzione per recuperare i valori necessari.

ASP.NET Core ha introdotto un sistema di configurazione flessibile che consente di definire l'origine o le origini di configurazione usate dall'app e dalla distribuzione. L'infrastruttura ConfigurationBuilder che potrebbe essere in uso nell'app Web Forms è basa sui concetti usati nel sistema di configurazione ASP.NET Core.

Il seguente frammento di codice illustra come il progetto Web Forms eShop usa web.config per archiviare i valori di configurazione:

<configuration>
  <configSections>
    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
  </configSections>
  <connectionStrings>
    <add name="CatalogDBContext" connectionString="Data Source=(localdb)\MSSQLLocalDB; Initial Catalog=Microsoft.eShopOnContainers.Services.CatalogDb; Integrated Security=True; MultipleActiveResultSets=True;" providerName="System.Data.SqlClient" />
  </connectionStrings>
  <appSettings>
    <add key="UseMockData" value="true" />
    <add key="UseCustomizationData" value="false" />
  </appSettings>
</configuration>

È comune che segreti, ad esempio stringhe di connessione di database, vengano archiviati all'interno di web.config. I segreti sono inevitabilmente persistenti in posizioni non sicure, come il controllo del codice sorgente. Con Blazor in ASP.NET Core, la precedente configurazione basata su XML viene sostituita con il codice JSON seguente:

{
  "ConnectionStrings": {
    "CatalogDBContext": "Data Source=(localdb)\\MSSQLLocalDB; Initial Catalog=Microsoft.eShopOnContainers.Services.CatalogDb; Integrated Security=True; MultipleActiveResultSets=True;"
  },
  "UseMockData": true,
  "UseCustomizationData": false
}

JSON è il formato di configurazione predefinito; tuttavia, ASP.NET Core supporta molti altri formati, tra cui XML. Sono disponibili anche diversi formati supportati dalla community.

È possibile accedere ai valori di configurazione dal generatore in Program.cs:

if (builder.Configuration.GetValue<bool>("UseMockData"))
{
    builder.Services.AddSingleton<ICatalogService, CatalogServiceMock>();
}
else
{
    builder.Services.AddScoped<ICatalogService, CatalogService>();
    builder.Services.AddScoped<IDatabaseInitializer<CatalogDBContext>, CatalogDBInitializer>();
    builder.Services.AddSingleton<CatalogItemHiLoGenerator>();
    builder.Services.AddScoped(_ => new CatalogDBContext(builder.Configuration.GetConnectionString("CatalogDBContext")));
}

Per impostazione predefinita, variabili di ambiente, file JSON (appsettings.json e appsettings.{ Environment}.json) e opzioni della riga di comando vengono registrati come origini di configurazione valide nell'oggetto di configurazione. È possibile accedere alle origini di configurazione tramite Configuration[key]. Una tecnica più avanzata consiste nell'associare i dati di configurazione agli oggetti usando il criterio di opzioni. Per ulteriori informazioni sulla configurazione e sul modello di opzioni, consultare Configurazione in ASP.NET Core e Criterio di opzioni in ASP.NET Core.

Eseguire la migrazione dell'accesso ai dati

L'accesso ai dati è un aspetto importante di qualsiasi app. Il progetto eShop archivia le informazioni sul catalogo in un database e recupera i dati con Entity Framework (EF) 6. Poiché EF 6 è supportato in .NET 5, il progetto può continuare a usarlo.

Per eShop, sono state necessarie le seguenti modifiche relative a EF:

  • In .NET Framework, l'oggetto DbContext accetta una stringa del formato name=ConnectionString e usa la stringa di connessione da ConfigurationManager.AppSettings[ConnectionString] per connettersi. In .NET Core, quest’operazione non è supportata. È necessario fornire la stringa di connessione.
  • L'accesso al database è stato eseguito in modo sincrono. Sebbene questo metodo funzioni, la scalabilità potrebbe risentirne. Questa logica deve essere spostata in un modello asincrono.

Anche se lo stesso supporto nativo non è disponibile per l'associazione di set di dati, Blazor offre flessibilità e potenza con il suo supporto C# in una pagina Razor. Ad esempio, è possibile eseguire calcoli e visualizzarne il risultato. Per ulteriori informazioni sui criteri di dati in Blazor, consultare il capitolo Accesso ai dati.

Modifiche dell'architettura

Infine, esistono alcune importanti differenze di architettura da considerare durante la migrazione a Blazor. Molte di queste modifiche sono applicabili a qualsiasi elemento basato su .NET Core o ASP.NET Core.

Poiché Blazor si basa su .NET Core, ci sono considerazioni nel garantire il supporto in .NET Core. Alcune delle principali modifiche includono la rimozione delle seguenti funzionalità:

  • Più AppDomain
  • Gestione remota
  • Sicurezza dall'accesso di codice (CAS, Code Access Security)
  • Trasparenza della sicurezza

Per ulteriori informazioni sulle tecniche per identificare le modifiche necessarie a supportare l'esecuzione in .NET Core, consultare Convertire il codice da .NET Framework a .NET Core.

ASP.NET Core è una versione rivisitata di ASP.NET e presenta alcune modifiche che potrebbero inizialmente non sembrare ovvie. Le modifiche principali sono:

  • Nessun contesto di sincronizzazione, il che significa che non sono presenti HttpContext.Current, Thread.CurrentPrincipal o altre funzioni di accesso statiche
  • Nessuna copia shadow
  • Nessuna coda di richieste

Molte operazioni in ASP.NET Core sono asincrone, il che semplifica l'offload delle attività associate a I/O. È importante non bloccare mai usando Task.Wait() o Task.GetResult(), poiché questo potrebbe esaurire rapidamente le risorse del pool di thread.

Conclusione della migrazione

A questo punto, sono stati illustrati molti esempi di come spostare un progetto Web Forms in Blazor. Per un esempio completo, consultare il progetto eShopOnBlazor.