Udostępnij za pośrednictwem


Migrowanie z ASP.NET Web Forms do Blazor

Napiwek

Ta zawartość jest fragmentem książki eBook Blazor dla deweloperów formularzy internetowych platformy ASP NET dla platformy Azure, dostępnym na platformie .NET Docs lub jako bezpłatny plik PDF do pobrania, który można odczytać w trybie offline.

Blazor-for-ASP-NET-Web-Forms-Developers eBook cover thumbnail (Miniatura książki eBook for-ASP-NET-Web-Forms-Developers).

Migrowanie bazy kodu z ASP.NET Web Forms do Blazor programu to czasochłonne zadanie, które wymaga planowania. W tym rozdziale opisano proces. Czymś, co może ułatwić przejście, jest zapewnienie, że aplikacja jest zgodna z architekturą N-warstwową , gdzie w modelu aplikacji (w tym przypadku formularze sieci Web) jest oddzielona od logiki biznesowej. Ta logiczna separacja warstw wyjaśnia, co należy przenieść do platformy .NET Core i Blazor.

W tym przykładzie jest używana aplikacja eShop dostępna w usłudze GitHub . eShop to usługa wykazu, która zapewnia funkcje CRUD za pośrednictwem wpisu formularza i weryfikacji.

Dlaczego działająca aplikacja powinna zostać zmigrowana do Blazor? Wiele razy nie ma potrzeby. ASP.NET web forms będzie nadal obsługiwana przez wiele lat. Jednak wiele funkcji, które udostępnia, Blazor są obsługiwane tylko w migrowanej aplikacji. Takie funkcje obejmują:

  • Ulepszenia wydajności w strukturze, takie jak Span<T>
  • Możliwość uruchamiania jako WebAssembly
  • Obsługa wielu platform dla systemów Linux i macOS
  • Wdrażanie lokalne aplikacji lub wdrażanie platformy udostępnionej bez wpływu na inne aplikacje

Jeśli te lub inne nowe funkcje są wystarczająco atrakcyjne, może istnieć wartość migracji aplikacji. Migracja może mieć różne kształty; może to być cała aplikacja lub tylko niektóre punkty końcowe, które wymagają zmian. Decyzja o migracji jest ostatecznie oparta na problemach biznesowych, które mają zostać rozwiązane przez dewelopera.

Hosting po stronie serwera a po stronie klienta

Zgodnie z opisem w rozdziale modele hostingu aplikacja może być hostowana Blazor na dwa różne sposoby: po stronie serwera i po stronie klienta. Model po stronie serwera używa połączeń ASP.NET Core SignalR do zarządzania aktualizacjami DOM podczas uruchamiania dowolnego rzeczywistego kodu na serwerze. Model po stronie klienta działa w WebAssembly przeglądarce i nie wymaga połączeń z serwerem. Istnieje wiele różnic, które mogą mieć wpływ na najlepszą aplikację:

  • Uruchamianie jako WebAssembly nie obsługuje wszystkich funkcji (takich jak wątkowanie) w bieżącej chwili
  • Rozmowa między klientem a serwerem może powodować problemy z opóźnieniami w trybie po stronie serwera
  • Dostęp do baz danych i usług wewnętrznych lub chronionych wymaga oddzielnej usługi z hostingiem po stronie klienta

W momencie pisania modelu po stronie serwera bardziej przypomina formularze sieci Web. Większość tego rozdziału koncentruje się na modelu hostingu po stronie serwera, ponieważ jest gotowy do produkcji.

Tworzenie nowego projektu

Ten początkowy krok migracji polega na utworzeniu nowego projektu. Ten typ projektu jest oparty na projektach w stylu zestawu SDK platformy .NET i upraszcza większość standardowych projektów używanych w poprzednich formatach projektu. Aby uzyskać więcej informacji, zobacz rozdział dotyczący struktury projektu.

Po utworzeniu projektu zainstaluj biblioteki, które były używane w poprzednim projekcie. W starszych projektach formularzy sieci Web być może użyto pliku packages.config , aby wyświetlić listę wymaganych pakietów NuGet. W nowym projekcie w stylu zestawu SDK plik packages.config został zastąpiony elementami <PackageReference> w pliku projektu. Zaletą tego podejścia jest to, że wszystkie zależności są instalowane przechodnio. Wystarczy wyświetlić tylko zależności najwyższego poziomu, o których ci zależy.

Wiele używanych zależności jest dostępnych dla platformy .NET, w tym dla platformy Entity Framework 6 i log4net. Jeśli nie jest dostępna żadna wersja platformy .NET lub .NET Standard, często można użyć wersji programu .NET Framework. Przebieg może się różnić. Każdy używany interfejs API, który nie jest dostępny na platformie .NET, powoduje błąd środowiska uruchomieniowego. Program Visual Studio powiadamia o takich pakietach. Żółta ikona zostanie wyświetlona w węźle Odwołania projektu w Eksplorator rozwiązań.

W projekcie eShop opartym na oprogramowaniu Blazormożna zobaczyć zainstalowane pakiety. Wcześniej plik packages.config wyświetlił każdy pakiet używany w projekcie, co powoduje, że plik ma prawie 50 wierszy długości. Fragment pliku packages.config to:

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

Element <packages> zawiera wszystkie niezbędne zależności. Trudno jest zidentyfikować, które z tych pakietów są uwzględnione, ponieważ są one wymagane. Niektóre <package> elementy są wyświetlane po prostu w celu zaspokojenia wymaganych potrzeb zależności.

Projekt Blazor zawiera listę zależności wymaganych w elemencie <ItemGroup> w pliku projektu:

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

Jednym pakietem NuGet, który upraszcza życie deweloperów formularzy internetowych, jest pakiet zgodności systemu Windows. Chociaż platforma .NET jest międzyplatformowa, niektóre funkcje są dostępne tylko w systemie Windows. Funkcje specyficzne dla systemu Windows są udostępniane przez zainstalowanie pakietu zgodności. Przykładami takich funkcji są usługi Rejestru, WMI i Katalogowe. Pakiet dodaje około 20 000 interfejsów API i aktywuje wiele usług, za pomocą których możesz już znać. Projekt eShop nie wymaga pakietu zgodności; ale jeśli projekty korzystają z funkcji specyficznych dla systemu Windows, pakiet ułatwia migrację.

Włączanie procesu uruchamiania

Proces uruchamiania dla programu Blazor został zmieniony z formularzy sieci Web i jest zgodny z podobną konfiguracją dla innych usług ASP.NET Core. Po stronie serwera składniki Razor są uruchamiane w ramach normalnej aplikacji ASP.NET Core. W przypadku hostowania w przeglądarce za pomocą WebAssemblyskładników Razor używany jest podobny model hostingu. Różnica polega na tym, że składniki są uruchamiane jako oddzielna usługa od dowolnego z procesów zaplecza. Tak czy inaczej, uruchomienie jest podobne.

Plik Global.asax.cs jest domyślną stroną uruchamiania dla projektów web forms. W projekcie eShop ten plik konfiguruje kontener Inversion of Control (IoC) i obsługuje różne zdarzenia cyklu życia aplikacji lub żądania. Niektóre z tych zdarzeń są obsługiwane za pomocą oprogramowania pośredniczącego (na przykład Application_BeginRequest). Inne zdarzenia wymagają zastąpienia określonych usług za pośrednictwem wstrzykiwania zależności (DI).

Na przykład plik Global.asax.cs dla eShop zawiera następujący kod:

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

Powyższy plik staje się plikiem Program.cs po stronie Blazorserwera:

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

Jedną z znaczących zmian, które można zauważyć w formularzach sieci Web, jest znaczenie wstrzykiwania zależności (DI). Di jest zasadą przewodnią w projekcie ASP.NET Core. Obsługuje dostosowywanie prawie wszystkich aspektów platformy ASP.NET Core. Istnieje nawet wbudowany dostawca usług, który może być używany w wielu scenariuszach. Jeśli wymagana jest większa liczba dostosowań, może być obsługiwana przez wiele projektów społeczności. Możesz na przykład przeprowadzić inwestycję biblioteki DI innej firmy.

W oryginalnej aplikacji eShop istnieje pewna konfiguracja zarządzania sesjami. Ponieważ po stronie Blazor serwera do komunikacji używa ASP.NET Core SignalR, stan sesji nie jest obsługiwany, ponieważ połączenia mogą występować niezależnie od kontekstu HTTP. Aplikacja korzystająca ze stanu sesji wymaga ponownego utworzenia architektury przed uruchomieniem Blazor jako aplikacja.

Aby uzyskać więcej informacji na temat uruchamiania aplikacji, zobacz Uruchamianie aplikacji.

Migrowanie modułów HTTP i programów obsługi do oprogramowania pośredniczącego

Moduły HTTP i procedury obsługi są typowymi wzorcami w formularzach Web Forms w celu kontrolowania potoku żądania HTTP. Klasy, które implementują IHttpModule lub IHttpHandler mogą być zarejestrowane i przetwarzają żądania przychodzące. Web Forms konfiguruje moduły i programy obsługi w pliku web.config . Formularze internetowe są również silnie oparte na obsłudze zdarzeń cyklu życia aplikacji. ASP.NET Core używa zamiast tego oprogramowania pośredniczącego. Oprogramowanie pośredniczące jest rejestrowane w Configure metodzie Startup klasy . Kolejność wykonywania oprogramowania pośredniczącego jest określana przez zamówienie rejestracji.

W sekcji Włączanie procesu uruchamiania zdarzenie cyklu życia zostało zgłoszone przez formularze web forms jako metodęApplication_BeginRequest. To zdarzenie nie jest dostępne w programie ASP.NET Core. Jednym ze sposobów osiągnięcia tego zachowania jest zaimplementowanie oprogramowania pośredniczącego, jak pokazano w przykładzie pliku Startup.cs . To oprogramowanie pośredniczące wykonuje tę samą logikę, a następnie przekazuje kontrolkę do następnej procedury obsługi w potoku oprogramowania pośredniczącego.

Aby uzyskać więcej informacji na temat migrowania modułów i procedur obsługi, zobacz Migrowanie programów obsługi HTTP i modułów do oprogramowania pośredniczącego ASP.NET Core.

Migrowanie plików statycznych

Aby obsługiwać pliki statyczne (na przykład HTML, CSS, obrazy i JavaScript), pliki muszą być udostępniane przez oprogramowanie pośredniczące. UseStaticFiles Wywołanie metody umożliwia obsługę plików statycznych ze ścieżki głównej sieci Web. Domyślny katalog główny sieci Web to wwwroot, ale można go dostosować. Zgodnie z plikiem Program.cs :

...

app.UseStaticFiles();

...

Projekt eShop umożliwia podstawowy dostęp do plików statycznych. Istnieje wiele dostosowań dostępnych na potrzeby dostępu do plików statycznych. Aby uzyskać informacje na temat włączania plików domyślnych lub przeglądarki plików, zobacz Pliki statyczne w programie ASP.NET Core.

Migrowanie konfiguracji pakietów i minification środowiska uruchomieniowego

Tworzenie pakietów i minimalizowanie to techniki optymalizacji wydajności w celu zmniejszenia liczby i rozmiaru żądań serwera w celu pobrania niektórych typów plików. Języki JavaScript i CSS często przechodzą jakąś formę tworzenia pakietów lub minifikacji przed wysłaniem do klienta. W ASP.NET web forms te optymalizacje są obsługiwane w czasie wykonywania. Konwencje optymalizacji są definiowane w pliku App_Start/BundleConfig.cs . W ASP.NET Core przyjęto bardziej deklaratywne podejście. Plik zawiera listę plików, które mają być zminimalizowane, wraz z określonymi ustawieniami minyfikacji.

Aby uzyskać więcej informacji na temat tworzenia pakietów i minification, zobacz Bundle and minify static assets in ASP.NET Core (Łączenie i minimalizowanie zasobów statycznych w usłudze ASP.NET Core).

Migrowanie stron ASPX

Strona w aplikacji Web Forms to plik z rozszerzeniem .aspx . Strona formularzy sieci Web może być często mapowana na składnik w programie Blazor. Składnik Razor jest utworzony w pliku z rozszerzeniem razor . W przypadku projektu eShop pięć stron jest konwertowanych na stronę Razor.

Na przykład widok szczegółów składa się z trzech plików w projekcie Web Forms: Details.aspx, Details.aspx.cs i Details.aspx.designer.cs. Podczas konwertowania na Blazorelement kod i znaczniki są łączone w plik Details.razor. Kompilacja Razor (równoważna zawartości w plikach .designer.cs) jest przechowywana w katalogu obj i nie jest domyślnie widoczna w Eksplorator rozwiązań. Strona Formularze sieci Web składa się z następujących znaczników:

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

Powyższy kod znaczników zawiera następujący kod:

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

Po przekonwertowaniu na Blazorelement strona formularzy sieci Web przekłada się na następujący kod:

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

Zwróć uwagę, że kod i znaczniki znajdują się w tym samym pliku. Wszystkie wymagane usługi są dostępne za pomocą atrybutu @inject . Zgodnie z dyrektywą @page dostęp do tej strony można uzyskać na Catalog/Details/{id} trasie. Wartość symbolu zastępczego trasy {id} została ograniczona do liczby całkowitej. Zgodnie z opisem w sekcji routingu, w przeciwieństwie do formularzy internetowych, składnik Razor jawnie określa trasę i wszystkie dołączone parametry. Wiele kontrolek formularzy sieci Web może nie mieć dokładnych odpowiedników w programie Blazor. Często istnieje równoważny fragment kodu HTML, który będzie służył w tym samym celu. Na przykład kontrolkę <asp:Label /> można zastąpić elementem HTML <label> .

Walidacja modelu w programie Blazor

Jeśli kod formularzy sieci Web zawiera walidację, możesz przenieść większość elementów, które mają niewielkie zmiany. Zaletą uruchamiania w programie Blazor jest to, że można uruchomić tę samą logikę walidacji bez konieczności używania niestandardowego kodu JavaScript. Adnotacje danych umożliwiają łatwą walidację modelu.

Na przykład strona Create.aspx zawiera formularz wprowadzania danych z walidacją. Przykładowy fragment kodu wygląda następująco:

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

W Blazorpliku , równoważny znacznik jest udostępniany w pliku 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>

Kontekst EditForm obejmuje obsługę walidacji i może być opakowany wokół danych wejściowych. Adnotacje danych są typowym sposobem dodawania walidacji. Taką obsługę walidacji można dodać za pośrednictwem DataAnnotationsValidator składnika. Aby uzyskać więcej informacji na temat tego mechanizmu, zobacz ASP.NET Podstawowe Blazor formularze i walidacja.

Migrowanie konfiguracji

W projekcie web forms dane konfiguracji są najczęściej przechowywane w pliku web.config . Dostęp do danych konfiguracji jest uzyskiwany za pomocą polecenia ConfigurationManager. Usługi były często wymagane do analizowania obiektów. W programie .NET Framework 4.7.2 komponowanie zostało dodane do konfiguracji za pośrednictwem programu ConfigurationBuilders. Ci konstruktorzy umożliwili deweloperom dodawanie różnych źródeł konfiguracji, które następnie składały się w czasie wykonywania w celu pobrania niezbędnych wartości.

ASP.NET Core wprowadził elastyczny system konfiguracji, który umożliwia zdefiniowanie źródła konfiguracji lub źródeł używanych przez aplikację i wdrożenie. Infrastruktura ConfigurationBuilder , która może być używana w aplikacji Web Forms, została wymodelowana po pojęciach używanych w systemie konfiguracji ASP.NET Core.

Poniższy fragment kodu pokazuje, jak projekt Web Forms eShop używa pliku web.config do przechowywania wartości konfiguracji:

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

Często wpisy tajne, takie jak parametry połączenia bazy danych, są przechowywane w pliku web.config. Wpisy tajne są nieuchronnie utrwalane w niezabezpieczonych lokalizacjach, takich jak kontrola źródła. W Blazor przypadku ASP.NET Core poprzednia konfiguracja oparta na formacie XML jest zastępowana następującym kodem JSON:

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

Format JSON jest domyślnym formatem konfiguracji; jednak ASP.NET Core obsługuje wiele innych formatów, w tym XML. Istnieje również kilka formatów obsługiwanych przez społeczność.

Dostęp do wartości konfiguracji można uzyskać z poziomu konstruktora w 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")));
}

Domyślnie zmienne środowiskowe, pliki JSON (appsettings.json i appsettings.{ Środowisko}.json) i opcje wiersza polecenia są rejestrowane jako prawidłowe źródła konfiguracji w obiekcie konfiguracji. Dostęp do źródeł konfiguracji można uzyskać za pośrednictwem programu Configuration[key]. Bardziej zaawansowaną techniką jest powiązanie danych konfiguracji z obiektami przy użyciu wzorca opcji. Aby uzyskać więcej informacji na temat konfiguracji i wzorca opcji, zobacz Configuration in ASP.NET Core and Options pattern in ASP.NET Core ,, odpowiednio.

Migrowanie dostępu do danych

Dostęp do danych jest ważnym aspektem każdej aplikacji. Projekt eShop przechowuje informacje o katalogu w bazie danych i pobiera dane za pomocą programu Entity Framework (EF) 6. Ponieważ program EF 6 jest obsługiwany na platformie .NET 5, projekt może nadal go używać.

Następujące zmiany związane z ef były niezbędne do eShop:

  • W programie .NET Framework DbContext obiekt akceptuje ciąg formularza name=ConnectionString i używa parametry połączenia z ConfigurationManager.AppSettings[ConnectionString] , aby nawiązać połączenie. W programie .NET Core nie jest to obsługiwane. Należy podać parametry połączenia.
  • Dostęp do bazy danych był uzyskiwany w sposób synchroniczny. Chociaż to działa, skalowalność może cierpieć. Ta logika powinna zostać przeniesiona do wzorca asynchronicznego.

Chociaż nie ma tej samej natywnej obsługi powiązania zestawu danych, Blazor zapewnia elastyczność i możliwości dzięki obsłudze języka C# na stronie Razor. Na przykład można wykonywać obliczenia i wyświetlać wynik. Aby uzyskać więcej informacji na temat wzorców danych w programie Blazor, zobacz rozdział Dostęp do danych.

Zmiany w architekturze

Na koniec istnieją pewne ważne różnice w architekturze, które należy wziąć pod uwagę podczas migracji do Blazorprogramu . Wiele z tych zmian ma zastosowanie do dowolnych elementów opartych na platformie .NET Core lub ASP.NET Core.

Ponieważ Blazor jest oparta na platformie .NET Core, należy wziąć pod uwagę zapewnienie obsługi platformy .NET Core. Niektóre z głównych zmian obejmują usunięcie następujących funkcji:

  • Wiele domen aplikacji
  • Komunikacja zdalna
  • Zabezpieczenia dostępu kodu (CAS)
  • Przezroczystość zabezpieczeń

Aby uzyskać więcej informacji na temat technik identyfikowania niezbędnych zmian do obsługi uruchomionych na platformie .NET Core, zobacz Przenoszenie kodu z programu .NET Framework do platformy .NET Core.

ASP.NET Core to przeimaginowana wersja ASP.NET i ma pewne zmiany, które początkowo nie wydają się oczywiste. Główne zmiany to:

  • Brak kontekstu synchronizacji, co oznacza, że nie HttpContext.Currentma żadnych metod dostępu statycznych , Thread.CurrentPrincipallub
  • Brak kopiowania w tle
  • Brak kolejki żądań

Wiele operacji w ASP.NET Core jest asynchronicznych, co ułatwia ładowanie zadań związanych z operacjami we/wy. Ważne jest, aby nigdy nie blokować przy użyciu funkcji Task.Wait() lub Task.GetResult(), co może szybko wyczerpać zasoby puli wątków.

Zakończenie migracji

W tym momencie pokazano wiele przykładów potrzebnych do przeniesienia projektu web forms do usługi Blazor. Pełny przykład można znaleźć w projekcie eShopOnBlazor .