BlazorZaawansowane scenariusze ASP.NET CoreASP.NET Core Blazor advanced scenarios

Blazor Server Procedura obsługi obwoduBlazor Server circuit handler

Blazor Server umożliwia kodowi Definiowanie procedury obsługi obwodu, która umożliwia uruchamianie kodu na zmiany stanu obwodu użytkownika.Blazor Server allows code to define a circuit handler, which allows running code on changes to the state of a user's circuit. Procedura obsługi obwodu jest implementowana przez wyprowadzanie z CircuitHandler i rejestrowanie klasy w kontenerze usługi aplikacji.A circuit handler is implemented by deriving from CircuitHandler and registering the class in the app's service container. Poniższy przykład obsługi obwodu śledzi otwarte SignalR połączenia:The following example of a circuit handler tracks open SignalR connections:

using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Server.Circuits;

public class TrackingCircuitHandler : CircuitHandler
{
    private HashSet<Circuit> circuits = new HashSet<Circuit>();

    public override Task OnConnectionUpAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        circuits.Add(circuit);

        return Task.CompletedTask;
    }

    public override Task OnConnectionDownAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        circuits.Remove(circuit);

        return Task.CompletedTask;
    }

    public int ConnectedCircuits => circuits.Count;
}

Procedury obsługi obwodu są rejestrowane przy użyciu funkcji DI.Circuit handlers are registered using DI. Wystąpienia w zakresie są tworzone na wystąpienie obwodu.Scoped instances are created per instance of a circuit. Korzystając z TrackingCircuitHandler powyższego przykładu, tworzona jest usługa singleton, ponieważ stan wszystkich obwodów musi być śledzony:Using the TrackingCircuitHandler in the preceding example, a singleton service is created because the state of all circuits must be tracked:

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddSingleton<CircuitHandler, TrackingCircuitHandler>();
}

Jeśli metody obsługi niestandardowego obwodu zgłaszają nieobsługiwany wyjątek, wyjątek jest krytyczny dla Blazor Server obwodu.If a custom circuit handler's methods throw an unhandled exception, the exception is fatal to the Blazor Server circuit. Aby tolerować wyjątki w kodzie programu obsługi lub metodach wywoływanych, zawiń kod w jednej lub kilku try-catch instrukcjach z obsługą błędów i rejestrowaniem.To tolerate exceptions in a handler's code or called methods, wrap the code in one or more try-catch statements with error handling and logging.

Gdy obwód kończy się, ponieważ użytkownik odłączył się i struktura czyści stan obwodu, struktura usuwa zakres DI obwodu.When a circuit ends because a user has disconnected and the framework is cleaning up the circuit state, the framework disposes of the circuit's DI scope. Oddysponowanie zakresu polega na usunięciu wszelkich usług DI-Scope w zakresie, które implementują System.IDisposable .Disposing the scope disposes any circuit-scoped DI services that implement System.IDisposable. Jeśli jakakolwiek usługa nie zgłasza nieobsłużonego wyjątku podczas usuwania, struktura rejestruje wyjątek.If any DI service throws an unhandled exception during disposal, the framework logs the exception.

Ręczna logika RenderTreeBuilderManual RenderTreeBuilder logic

RenderTreeBuilder zapewnia metody manipulowania składnikami i elementami, w tym ręczne Kompilowanie składników w kodzie C#.RenderTreeBuilder provides methods for manipulating components and elements, including building components manually in C# code.

Uwaga

Korzystanie z programu RenderTreeBuilder do tworzenia składników jest zaawansowanym scenariuszem.Use of RenderTreeBuilder to create components is an advanced scenario. Nieprawidłowo sformułowany składnik (na przykład niezamknięty tag znacznika) może spowodować niezdefiniowane zachowanie.A malformed component (for example, an unclosed markup tag) can result in undefined behavior.

Rozważmy następujący PetDetails składnik, który można ręcznie utworzyć w innym składniku:Consider the following PetDetails component, which can be manually built into another component:

<h2>Pet Details Component</h2>

<p>@PetDetailsQuote</p>

@code
{
    [Parameter]
    public string PetDetailsQuote { get; set; }
}

W poniższym przykładzie pętla w CreateComponent metodzie generuje trzy PetDetails składniki.In the following example, the loop in the CreateComponent method generates three PetDetails components. W RenderTreeBuilder metodach z numerem sekwencyjnym numery sekwencji są numerami wierszy kodu źródłowego.In RenderTreeBuilder methods with a sequence number, sequence numbers are source code line numbers. BlazorAlgorytm różnic polega na numerach sekwencji odpowiadających odrębnym wierszom kodu, a nie odrębnym wywoływaniu wywołań.The Blazor difference algorithm relies on the sequence numbers corresponding to distinct lines of code, not distinct call invocations. Podczas tworzenia składnika przy użyciu RenderTreeBuilder metod umieszczaj argumenty dla numerów sekwencji.When creating a component with RenderTreeBuilder methods, hardcode the arguments for sequence numbers. Użycie obliczenia lub licznika do wygenerowania numeru sekwencji może prowadzić do niskiej wydajności.Using a calculation or counter to generate the sequence number can lead to poor performance. Aby uzyskać więcej informacji, zobacz sekcję numery sekwencji powiązane z numerami wierszy kodu i kolejnością niewykonania .For more information, see the Sequence numbers relate to code line numbers and not execution order section.

BuiltContent składnikaBuiltContent component:

@page "/BuiltContent"

<h1>Build a component</h1>

@CustomRender

<button type="button" @onclick="RenderComponent">
    Create three Pet Details components
</button>

@code {
    private RenderFragment CustomRender { get; set; }
    
    private RenderFragment CreateComponent() => builder =>
    {
        for (var i = 0; i < 3; i++) 
        {
            builder.OpenComponent(0, typeof(PetDetails));
            builder.AddAttribute(1, "PetDetailsQuote", "Someone's best friend!");
            builder.CloseComponent();
        }
    };    
    
    private void RenderComponent()
    {
        CustomRender = CreateComponent();
    }
}

Ostrzeżenie

Typy w programie Microsoft.AspNetCore.Components.RenderTree umożliwiają przetwarzanie wyników operacji renderowania.The types in Microsoft.AspNetCore.Components.RenderTree allow processing of the results of rendering operations. Są to wewnętrzne szczegóły Blazor implementacji platformy.These are internal details of the Blazor framework implementation. Te typy powinny być uznawane za niestabilne i mogą ulec zmianie w przyszłych wersjach.These types should be considered unstable and subject to change in future releases.

Numery sekwencji odnoszą się do numerów wierszy kodu, a nie kolejności wykonywaniaSequence numbers relate to code line numbers and not execution order

Razor Pliki składników ( .razor ) są zawsze kompilowane.Razor component files (.razor) are always compiled. Kompilacja jest potencjalną możliwością przekroczenia interpretacji kodu, ponieważ krok kompilacji może służyć do iniekcji informacji, które zwiększają wydajność aplikacji w czasie wykonywania.Compilation is a potential advantage over interpreting code because the compile step can be used to inject information that improves app performance at runtime.

Najważniejszym przykładem tych ulepszeń są numery sekwencji.A key example of these improvements involves sequence numbers. Numery sekwencji wskazują na środowisko uruchomieniowe, które pochodzą z różnych i uporządkowanych wierszy kodu.Sequence numbers indicate to the runtime which outputs came from which distinct and ordered lines of code. Środowisko uruchomieniowe używa tych informacji do generowania wydajnych różnic drzewa w czasie liniowym, które są znacznie szybsze niż zwykle jest to możliwe dla algorytmu różnicowego drzewa ogólnego.The runtime uses this information to generate efficient tree diffs in linear time, which is far faster than is normally possible for a general tree diff algorithm.

Weź pod uwagę następujący Razor plik składnika ( .razor ):Consider the following Razor component (.razor) file:

@if (someFlag)
{
    <text>First</text>
}

Second

Poprzedni kod kompiluje się w taki sposób, aby wyglądał następująco:The preceding code compiles to something like the following:

if (someFlag)
{
    builder.AddContent(0, "First");
}

builder.AddContent(1, "Second");

Gdy kod jest wykonywany po raz pierwszy, jeśli someFlag jest true , Konstruktor odbiera:When the code executes for the first time, if someFlag is true, the builder receives:

SequenceSequence TypType DaneData
00 Węzeł tekstuText node PierwszeFirst
11 Węzeł tekstuText node SecondSecond

Wyobraź sobie someFlag , że zostanie ona false przerenderowana, a znaczniki są renderowane ponownie.Imagine that someFlag becomes false, and the markup is rendered again. Tym razem Konstruktor odbiera:This time, the builder receives:

SequenceSequence TypType DaneData
11 Węzeł tekstuText node SecondSecond

Gdy środowisko uruchomieniowe wykonuje porównanie, zobaczy, że element w sekwencji 0 został usunięty, więc generuje następujący skrypt uproszczonej edycji:When the runtime performs a diff, it sees that the item at sequence 0 was removed, so it generates the following trivial edit script:

  • Usuń pierwszy węzeł tekstu.Remove the first text node.

Problem z programowo generowanymi numerami sekwencjiThe problem with generating sequence numbers programmatically

Wyobraź sobie, że została zapisana następująca logika konstruktora drzewa renderowania:Imagine instead that you wrote the following render tree builder logic:

var seq = 0;

if (someFlag)
{
    builder.AddContent(seq++, "First");
}

builder.AddContent(seq++, "Second");

Teraz pierwsze dane wyjściowe to:Now, the first output is:

SequenceSequence TypType DaneData
00 Węzeł tekstuText node PierwszeFirst
11 Węzeł tekstuText node SecondSecond

Ten wynik jest identyczny z poprzednim przypadkiem, dlatego nie istnieją żadne negatywne problemy.This outcome is identical to the prior case, so no negative issues exist. someFlag znajduje się false na drugim renderingu, a dane wyjściowe:someFlag is false on the second rendering, and the output is:

SequenceSequence TypType DaneData
00 Węzeł tekstuText node SecondSecond

Tym razem algorytm diff widzi, że pojawiły się dwie zmiany, a algorytm generuje następujący skrypt edycji:This time, the diff algorithm sees that two changes have occurred, and the algorithm generates the following edit script:

  • Zmień wartość pierwszego węzła tekstowego na Second .Change the value of the first text node to Second.
  • Usuń drugi węzeł tekstu.Remove the second text node.

Generowanie numerów sekwencji utraciło wszystkie przydatne informacje o tym, gdzie if/else znajdują się gałęzie i pętle w oryginalnym kodzie.Generating the sequence numbers has lost all the useful information about where the if/else branches and loops were present in the original code. Wynikiem tego jest różnica dwa razy , tak długo, jak wcześniej.This results in a diff twice as long as before.

Jest to prosty przykład.This is a trivial example. W bardziej realistycznych przypadkach ze złożonymi i głęboko zagnieżdżonymi strukturami, w szczególności z pętlami, koszt wydajności jest zwykle wyższy.In more realistic cases with complex and deeply nested structures, and especially with loops, the performance cost is usually higher. Zamiast natychmiastowego identyfikowania, które bloki lub gałęzie pętli zostały wstawione lub usunięte, algorytm diff musi odnosił się do drzewa renderowania.Instead of immediately identifying which loop blocks or branches have been inserted or removed, the diff algorithm has to recurse deeply into the render trees. Zwykle polega to na konieczności kompilowania dłużej edytowanych skryptów, ponieważ algorytm różnicowy jest niewiadome o tym, jak stare i nowe struktury odnoszą się do siebie.This usually results in having to build longer edit scripts because the diff algorithm is misinformed about how the old and new structures relate to each other.

Wskazówki i wnioskiGuidance and conclusions

  • Wydajność aplikacji ma wpływ na to, że numery sekwencji są generowane dynamicznie.App performance suffers if sequence numbers are generated dynamically.
  • Struktura nie może automatycznie tworzyć własnych numerów sekwencji w czasie wykonywania, ponieważ niezbędne informacje nie istnieją, chyba że są przechwytywane w czasie kompilacji.The framework can't create its own sequence numbers automatically at runtime because the necessary information doesn't exist unless it's captured at compile time.
  • Nie zapisuj długich bloków logiki wykonywanej ręcznie RenderTreeBuilder .Don't write long blocks of manually-implemented RenderTreeBuilder logic. Preferuj .razor pliki i Zezwalaj kompilatorowi na rozpatruje numery sekwencji.Prefer .razor files and allow the compiler to deal with the sequence numbers. Jeśli nie możesz uniknąć ręcznej RenderTreeBuilder logiki, Podziel długie bloki kodu na mniejsze fragmenty opakowane OpenRegion / CloseRegion .If you're unable to avoid manual RenderTreeBuilder logic, split long blocks of code into smaller pieces wrapped in OpenRegion/CloseRegion calls. Każdy region ma własne oddzielne miejsce numerów sekwencyjnych, więc można uruchomić ponownie od zera (lub dowolnego innego numeru) w każdym regionie.Each region has its own separate space of sequence numbers, so you can restart from zero (or any other arbitrary number) inside each region.
  • Jeśli numery sekwencji są stałee, algorytm diff wymaga tylko zwiększenia wartości sekwencji.If sequence numbers are hardcoded, the diff algorithm only requires that sequence numbers increase in value. Początkowa wartość i przerwy są nieistotne.The initial value and gaps are irrelevant. Jedną z wiarygodnych opcji jest użycie numeru wiersza kodu jako numeru sekwencyjnego lub rozpoczęcie od zera i zwiększenie według wartości lub setek (lub dowolnego preferowanego interwału).One legitimate option is to use the code line number as the sequence number, or start from zero and increase by ones or hundreds (or any preferred interval).
  • Blazor używa numerów sekwencji, podczas gdy inne struktury interfejsu użytkownika rozróżniania drzewa nie są używane.Blazor uses sequence numbers, while other tree-diffing UI frameworks don't use them. Różnica jest znacznie szybsza, gdy są używane numery sekwencyjne i Blazor ma zalety krok kompilacji, który zajmuje się automatycznie numerami sekwencyjnymi dla deweloperów tworzących .razor pliki.Diffing is far faster when sequence numbers are used, and Blazor has the advantage of a compile step that deals with sequence numbers automatically for developers authoring .razor files.