Partilhar via


Migrar de ASP.NET Web Forms para Blazor

Gorjeta

Este conteúdo é um excerto do eBook, Blazor para ASP NET Web Forms Developers for Azure, disponível no .NET Docs ou como um PDF transferível gratuito que pode ser lido offline.

Blazor-for-ASP-NET-Web-Forms-Developers miniatura da capa do eBook.

Migrar uma base de código de ASP.NET Web Forms para Blazor é uma tarefa demorada que requer planejamento. Este capítulo descreve o processo. Algo que pode facilitar a transição é garantir que o aplicativo siga uma arquitetura de N camadas , em que o modelo de aplicativo (neste caso, Web Forms) é separado da lógica de negócios. Essa separação lógica de camadas deixa claro o que precisa ser movido para o .NET Core e Blazoro .

Para este exemplo, o aplicativo eShop disponível no GitHub é usado. O eShop é um serviço de catálogo que fornece recursos CRUD por meio de entrada e validação de formulários.

Por que um aplicativo de trabalho deve ser migrado para Blazor? Muitas vezes, não há necessidade. ASP.NET Web Forms continuarão a ser suportados por muitos anos. No entanto, muitos dos recursos fornecidos Blazor são suportados apenas em um aplicativo migrado. Tais características incluem:

  • Melhorias de desempenho na estrutura, tais como: Span<T>
  • Capacidade de executar como WebAssembly
  • Suporte multiplataforma para Linux e macOS
  • Implantação local do aplicativo ou implantação de estrutura compartilhada sem afetar outros aplicativos

Se esses ou outros novos recursos forem atraentes o suficiente, pode haver valor na migração do aplicativo. A migração pode assumir diferentes formas; Pode ser o aplicativo inteiro ou apenas alguns pontos de extremidade que exigem as alterações. A decisão de migrar é, em última análise, baseada nos problemas de negócios a serem resolvidos pelo desenvolvedor.

Hospedagem do lado do servidor versus do lado do cliente

Conforme descrito no capítulo de modelos de hospedagem, um Blazor aplicativo pode ser hospedado de duas maneiras diferentes: do lado do servidor e do lado do cliente. O modelo do lado do servidor usa ASP.NET conexões Core SignalR para gerenciar as atualizações do DOM enquanto executa qualquer código real no servidor. O modelo do lado do cliente é executado como WebAssembly em um navegador e não requer conexões de servidor. Há uma série de diferenças que podem afetar o que é melhor para um aplicativo específico:

  • Executar como WebAssembly não suporta todos os recursos (como threading) no momento atual
  • A comunicação tagarela entre o cliente e o servidor pode causar problemas de latência no modo do lado do servidor
  • O acesso a bancos de dados e serviços internos ou protegidos requer um serviço separado com hospedagem do lado do cliente

No momento da escrita, o modelo do lado do servidor se assemelha mais aos Web Forms. A maior parte deste capítulo se concentra no modelo de hospedagem do lado do servidor, pois está pronto para produção.

Criar um novo projeto

Esta etapa inicial de migração é criar um novo projeto. Esse tipo de projeto é baseado nos projetos de estilo SDK do .NET e simplifica muito do clichê que foi usado em formatos de projeto anteriores. Para mais detalhes, consulte o capítulo sobre Estrutura do Projeto.

Uma vez que o projeto tenha sido criado, instale as bibliotecas que foram usadas no projeto anterior. Em projetos de Web Forms mais antigos, você pode ter usado o arquivo packages.config para listar os pacotes NuGet necessários. No novo projeto no estilo SDK, packages.config foi substituído por <PackageReference> elementos no arquivo de projeto. Um benefício dessa abordagem é que todas as dependências são instaladas transitivamente. Você lista apenas as dependências de nível superior que lhe interessam.

Muitas das dependências que você está usando estão disponíveis para .NET, incluindo o Entity Framework 6 e o log4net. Se não houver nenhuma versão .NET ou .NET Standard disponível, a versão do .NET Framework pode ser usada com frequência. A quilometragem pode variar. Qualquer API usada que não esteja disponível no .NET causa um erro de tempo de execução. O Visual Studio notifica você sobre esses pacotes. Um ícone amarelo aparece no nó Referências do projeto no Gerenciador de Soluções.

BlazorNo projeto eShop baseado em -based, você pode ver os pacotes que estão instalados. Anteriormente, o arquivo packages.config listava todos os pacotes usados no projeto, resultando em um arquivo de quase 50 linhas. Um trecho de 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>

O <packages> elemento inclui todas as dependências necessárias. É difícil identificar quais desses pacotes estão incluídos porque você precisa deles. Alguns <package> elementos são listados simplesmente para satisfazer as necessidades das dependências necessárias.

O Blazor projeto lista as dependências necessárias dentro de um <ItemGroup> elemento no arquivo de projeto:

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

Um pacote NuGet que simplifica a vida dos desenvolvedores de Web Forms é o Pacote de Compatibilidade do Windows. Embora o .NET seja multiplataforma, alguns recursos só estão disponíveis no Windows. Os recursos específicos do Windows são disponibilizados instalando o pacote de compatibilidade. Exemplos de tais recursos incluem o Registro, WMI e Serviços de Diretório. O pacote adiciona cerca de 20.000 APIs e ativa muitos serviços com os quais você já pode estar familiarizado. O projeto eShop não requer o pacote de compatibilidade; mas se seus projetos usam recursos específicos do Windows, o pacote facilita os esforços de migração.

Ativar processo de inicialização

O processo de inicialização para Blazor foi alterado de Web Forms e segue uma configuração semelhante para outros serviços ASP.NET Core. Quando hospedados no servidor, os componentes do Razor são executados como parte de um aplicativo ASP.NET Core normal. Quando hospedados no navegador com WebAssemblycomponentes do , o Razor usa um modelo de hospedagem semelhante. A diferença é que os componentes são executados como um serviço separado de qualquer um dos processos de back-end. De qualquer forma, a startup é semelhante.

O arquivo Global.asax.cs é a página de inicialização padrão para projetos de Web Forms. No projeto eShop, esse arquivo configura o contêiner Inversion of Control (IoC) e lida com os vários eventos do ciclo de vida do aplicativo ou solicitação. Alguns desses eventos são tratados com middleware (como Application_BeginRequest). Outros eventos exigem a substituição de serviços específicos por meio de injeção de dependência (DI).

A título de exemplo, o ficheiro Global.asax.cs para eShop, contém o seguinte código:

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

O arquivo anterior torna-se o arquivo Program.cs no lado Blazordo servidor:

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

Uma mudança significativa que você pode notar nos Web Forms é a proeminência da injeção de dependência (DI). DI tem sido um princípio orientador no projeto ASP.NET Core. Ele suporta a personalização de quase todos os aspetos da estrutura ASP.NET Core. Há até mesmo um provedor de serviços interno que pode ser usado para muitos cenários. Se for necessária mais personalização, ela pode ser apoiada por muitos projetos da comunidade. Por exemplo, você pode levar adiante seu investimento em biblioteca DI de terceiros.

No aplicativo eShop original, há algumas configurações para gerenciamento de sessão. Como o lado Blazor do servidor usa ASP.NET Core SignalR para comunicação, o estado da sessão não é suportado, pois as conexões podem ocorrer independentemente de um contexto HTTP. Um aplicativo que usa o estado da sessão requer rearquitetura antes de ser executado como um Blazor aplicativo.

Para obter mais informações sobre a inicialização do aplicativo, consulte Inicialização do aplicativo.

Migrar módulos HTTP e manipuladores para middleware

Módulos HTTP e manipuladores são padrões comuns em Web Forms para controlar o pipeline de solicitação HTTP. Classes que implementam IHttpModule ou IHttpHandler podem ser registradas e processam solicitações de entrada. Os Web Forms configuram módulos e manipuladores no arquivo web.config . Os Web Forms também se baseiam fortemente no tratamento de eventos do ciclo de vida do aplicativo. ASP.NET Core usa middleware. O middleware é registrado no Configure método da Startup classe. A ordem de execução do middleware é determinada pela ordem de registo.

Na seção Habilitar processo de inicialização, um evento de ciclo de vida foi gerado por Web Forms como o Application_BeginRequest método. Este evento não está disponível no ASP.NET Core. Uma maneira de conseguir esse comportamento é implementar middleware como visto no exemplo de arquivo Startup.cs . Esse middleware faz a mesma lógica e, em seguida, transfere o controle para o próximo manipulador no pipeline de middleware.

Para obter mais informações sobre como migrar módulos e manipuladores, consulte Migrar manipuladores HTTP e módulos para middleware ASP.NET Core.

Migrar arquivos estáticos

Para servir arquivos estáticos (por exemplo, HTML, CSS, imagens e JavaScript), os arquivos devem ser expostos por middleware. Chamar o UseStaticFiles método permite o serviço de arquivos estáticos a partir do caminho raiz da Web. O diretório raiz da Web padrão é wwwroot, mas pode ser personalizado. Conforme incluído no arquivo Program.cs:

...

app.UseStaticFiles();

...

O projeto eShop permite o acesso básico a arquivos estáticos. Há muitas personalizações disponíveis para acesso a arquivos estáticos. Para obter informações sobre como habilitar arquivos padrão ou um navegador de arquivos, consulte Arquivos estáticos no ASP.NET Core.

Migrar o agrupamento de tempo de execução e a configuração de minificação

A agregação e a minificação são técnicas de otimização de desempenho para reduzir o número e o tamanho das solicitações do servidor para recuperar determinados tipos de arquivos. JavaScript e CSS geralmente passam por alguma forma de agregação ou minificação antes de serem enviados para o cliente. Em ASP.NET Web Forms, essas otimizações são tratadas em tempo de execução. As convenções de otimização são definidas como um arquivo App_Start/BundleConfig.cs . No ASP.NET Core, adota-se uma abordagem mais declarativa. Um arquivo lista os arquivos a serem minificados, juntamente com configurações específicas de minificação.

Para obter mais informações sobre agregação e minificação, consulte Agrupar e minificar ativos estáticos no ASP.NET Core.

Migrar páginas ASPX

Uma página em um aplicativo Web Forms é um arquivo com a extensão .aspx . Uma página de Web Forms geralmente pode ser mapeada para um componente no Blazor. Um componente Razor é criado em um arquivo com a extensão .razor . Para o projeto eShop, cinco páginas são convertidas em uma página Razor.

Por exemplo, o modo de exibição de detalhes compreende três arquivos no projeto Web Forms: Details.aspx, Details.aspx.cs e Details.aspx.designer.cs. Ao converter para Blazor, o code-behind e a marcação são combinados em Details.razor. A compilação Razor (equivalente ao que está em .designer.cs arquivos) é armazenada no diretório obj e não é, por padrão, visível no Gerenciador de Soluções. A página Web Forms consiste na seguinte marcação:

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

O code-behind da marcação anterior inclui o seguinte código:

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

Quando convertida em Blazor, a página Web Forms se traduz no seguinte código:

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

Observe que o código e a marcação estão no mesmo arquivo. Todos os serviços necessários são disponibilizados com o @inject atributo. De acordo com a @page diretiva, esta página pode ser acessada Catalog/Details/{id} na rota. O valor do espaço reservado da rota {id} foi restrito a um inteiro. Conforme descrito na seção de roteamento, ao contrário dos Web Forms, um componente do Razor declara explicitamente sua rota e quaisquer parâmetros incluídos. Muitos controles Web Forms podem não ter contrapartidas exatas no Blazor. Muitas vezes, há um trecho HTML equivalente que servirá ao mesmo propósito. Por exemplo, o <asp:Label /> controle pode ser substituído por um elemento HTML <label> .

Validação do modelo em Blazor

Se o código dos Web Forms incluir validação, você poderá transferir muito do que tem com poucas ou nenhumas alterações. Um benefício da execução é Blazor que a mesma lógica de validação pode ser executada sem a necessidade de JavaScript personalizado. As anotações de dados permitem uma validação fácil do modelo.

Por exemplo, a página Create.aspx tem um formulário de entrada de dados com validação. Um trecho de exemplo ficaria assim:

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

No Blazor, a marcação equivalente é fornecida em um arquivo 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>

O EditForm contexto inclui suporte à validação e pode ser envolvido em torno de uma entrada. As anotações de dados são uma maneira comum de adicionar validação. Esse suporte de validação pode ser adicionado através do DataAnnotationsValidator componente. Para obter mais informações sobre esse mecanismo, consulte ASP.NET Formulários principais Blazor e validação.

Migrar configuração

Em um projeto de Web Forms, os dados de configuração são mais comumente armazenados no arquivo web.config . Os dados de configuração são acessados com ConfigurationManagero . Os serviços eram muitas vezes necessários para analisar objetos. Com o .NET Framework 4.7.2, a capacidade de composição foi adicionada à configuração via ConfigurationBuilders. Esses construtores permitiram que os desenvolvedores adicionassem várias fontes para a configuração que foi composta em tempo de execução para recuperar os valores necessários.

O ASP.NET Core introduziu um sistema de configuração flexível que permite definir a fonte ou fontes de configuração usadas pelo seu aplicativo e implantação. A ConfigurationBuilder infraestrutura que você pode estar usando em seu aplicativo Web Forms foi modelada de acordo com os conceitos usados no sistema de configuração ASP.NET Core.

O trecho a seguir demonstra como o projeto Web Forms eShop usa web.config para armazenar valores de configuração:

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

É comum que segredos, como cadeias de conexão de banco de dados, sejam armazenados no web.config. Os segredos são inevitavelmente persistidos em locais inseguros, como o controle do código-fonte. Com Blazor on ASP.NET Core, a configuração anterior baseada em XML é substituída pelo seguinte JSON:

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

JSON é o formato de configuração padrão; no entanto, o ASP.NET Core suporta muitos outros formatos, incluindo XML. Existem também vários formatos suportados pela comunidade.

Você pode acessar os valores de configuração do construtor em 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")));
}

Por padrão, variáveis de ambiente, arquivos JSON (appsettings.json e appsettings.{ Environment}.json) e as opções de linha de comando são registradas como fontes de configuração válidas no objeto de configuração. As fontes de configuração podem ser acessadas via Configuration[key]. Uma técnica mais avançada é vincular os dados de configuração a objetos usando o padrão de opções. Para obter mais informações sobre a configuração e o padrão de opções, consulte Configuração no ASP.NET Core e Padrão de Opções no ASP.NET Core, respectivamente.

Migrar acesso a dados

O acesso aos dados é um aspeto importante de qualquer aplicativo. O projeto eShop armazena informações de catálogo em um banco de dados e recupera os dados com o Entity Framework (EF) 6. Como o EF 6 é suportado no .NET 5, o projeto pode continuar a usá-lo.

As seguintes alterações relacionadas com a EF foram necessárias para a eShop:

  • No .NET Framework, o DbContext objeto aceita uma cadeia de caracteres do formulário name=ConnectionString e usa a cadeia de conexão de ConfigurationManager.AppSettings[ConnectionString] para se conectar. No .NET Core, isso não é suportado. A cadeia de conexão deve ser fornecida.
  • O banco de dados foi acessado de forma síncrona. Embora isso funcione, a escalabilidade pode ser prejudicada. Essa lógica deve ser movida para um padrão assíncrono.

Embora não haja o mesmo suporte nativo para vinculação de conjunto de dados, Blazor fornece flexibilidade e poder com seu suporte a C# em uma página do Razor. Por exemplo, você pode executar cálculos e exibir o resultado. Para obter mais informações sobre padrões de dados no Blazor, consulte o capítulo Acesso a dados.

Mudanças arquitetônicas

Finalmente, existem algumas diferenças arquitetônicas importantes a serem consideradas ao migrar para o Blazor. Muitas dessas alterações são aplicáveis a qualquer coisa baseada no .NET Core ou no ASP.NET Core.

Como Blazor é baseado no .NET Core, há considerações para garantir o suporte no .NET Core. Algumas das principais mudanças incluem a remoção dos seguintes recursos:

  • Vários AppDomains
  • Comunicação remota
  • Segurança de acesso ao código (CAS)
  • Transparência de Segurança

Para obter mais informações sobre técnicas para identificar as alterações necessárias para dar suporte à execução no .NET Core, consulte Portar seu código do .NET Framework para o .NET Core.

ASP.NET Core é uma versão reimaginada do ASP.NET e tem algumas mudanças que podem não parecer óbvias inicialmente. As principais alterações são as seguintes:

  • Sem contexto de sincronização, o que significa que não HttpContext.Currenthá , Thread.CurrentPrincipalou outros acessadores estáticos
  • Sem cópia de sombra
  • Sem fila de pedidos

Muitas operações no ASP.NET Core são assíncronas, o que permite um descarregamento mais fácil de tarefas ligadas a E/S. É importante nunca bloquear usando Task.Wait() ou Task.GetResult(), o que pode esgotar rapidamente os recursos do pool de threads.

Conclusão da migração

Neste ponto, você viu muitos exemplos do que é necessário para mover um projeto de Web Forms para Blazor. Para obter um exemplo completo, consulte o projeto eShopOnBlazor .