Создание и использование компонентов ASP.NET Core RazorCreate and use ASP.NET Core Razor components

Люк ЛаСаМ и Даниэль Roth)By Luke Latham and Daniel Roth

Просмотреть или скачать образец кода (как скачивать)View or download sample code (how to download)

Приложения блазор создаются с помощью компонентов.Blazor apps are built using components. Компонент — это автономный фрагмент пользовательского интерфейса, например страница, диалоговое окно или форма.A component is a self-contained chunk of user interface (UI), such as a page, dialog, or form. Компонент включает разметку HTML и логику обработки, необходимую для вставки данных или реагирования на события пользовательского интерфейса.A component includes HTML markup and the processing logic required to inject data or respond to UI events. Компоненты являются гибкими и легковесными.Components are flexible and lightweight. Они могут быть вложенными, повторно использованы и совместно использоваться проектами.They can be nested, reused, and shared among projects.

Классы компонентовComponent classes

Компоненты реализуются в файлах компонентов Razor ( . Razor) с помощью комбинации C# и HTML-разметки.Components are implemented in Razor component files (.razor) using a combination of C# and HTML markup. Компонент в Блазор формально называется компонентом Razor.A component in Blazor is formally referred to as a Razor component.

Имя компонента должно начинаться с символа верхнего регистра.A component's name must start with an uppercase character. Например, микулкомпонент. Razor является допустимым, а микулкомпонент. Razor является недопустимым.For example, MyCoolComponent.razor is valid, and myCoolComponent.razor is invalid.

Пользовательский интерфейс для компонента определяется с помощью HTML.The UI for a component is defined using HTML. Логика динамического отображения (например выражения, циклы и условные выражения) добавляется с помощью встроенного синтаксиса C# под названием Razor.Dynamic rendering logic (for example, loops, conditionals, expressions) is added using an embedded C# syntax called Razor. При компиляции приложения логика разметки и C# отрисовки HTML преобразуется в класс компонента.When an app is compiled, the HTML markup and C# rendering logic are converted into a component class. Имя созданного класса соответствует имени файла.The name of the generated class matches the name of the file.

Элементы класса компонента определяются в блоке @code.Members of the component class are defined in an @code block. В блоке @code состояние компонента (свойства, поля) указывается с помощью методов обработки событий или для определения другой логики компонента.In the @code block, component state (properties, fields) is specified with methods for event handling or for defining other component logic. Допускается более одного блока @code.More than one @code block is permissible.

Примечание

В предыдущих предварительных версиях ASP.NET Core 3,0 блоки @functions использовались для той же цели, что и блоки @code в компонентах Razor.In prior previews of ASP.NET Core 3.0, @functions blocks were used for the same purpose as @code blocks in Razor components. блоки @functions продолжают работать в компонентах Razor, но рекомендуется использовать блок @code в ASP.NET Core 3,0 Preview 6 или более поздней версии.@functions blocks continue to function in Razor components, but we recommend using the @code block in ASP.NET Core 3.0 Preview 6 or later.

Члены компонента можно использовать как часть логики визуализации компонента с помощью C# выражений, начинающихся с @.Component members can be used as part of the component's rendering logic using C# expressions that start with @. Например, C# поле отображается путем добавления префикса @ к имени поля.For example, a C# field is rendered by prefixing @ to the field name. В следующем примере вычисляется и выводится следующее:The following example evaluates and renders:

  • _headingFontStyle значение свойства CSS для font-style._headingFontStyle to the CSS property value for font-style.
  • _headingText содержимое элемента <h1>._headingText to the content of the <h1> element.
<h1 style="font-style:@_headingFontStyle">@_headingText</h1>

@code {
    private string _headingFontStyle = "italic";
    private string _headingText = "Put on your new Blazor!";
}

После первоначального отображения компонента Компонент повторно создает дерево рендеринга в ответ на события.After the component is initially rendered, the component regenerates its render tree in response to events. Затем блазор сравнивает новое дерево отрисовки с предыдущим и применяет любые изменения к модель DOMу (DOM) браузера.Blazor then compares the new render tree against the previous one and applies any modifications to the browser's Document Object Model (DOM).

Компоненты являются обычными C# классами и могут быть помещены в любое место внутри проекта.Components are ordinary C# classes and can be placed anywhere within a project. Компоненты, создающие веб-страницы, обычно находятся в папке страниц .Components that produce webpages usually reside in the Pages folder. Компоненты, не являющиеся страницами, часто помещаются в общую папку или пользовательскую папку, добавленную в проект.Non-page components are frequently placed in the Shared folder or a custom folder added to the project. Чтобы использовать пользовательскую папку, добавьте пространство имен пользовательской папки либо в родительский компонент, либо в файл _Imports. Razor приложения.To use a custom folder, add the custom folder's namespace to either the parent component or to the app's _Imports.razor file. Например, следующее пространство имен делает компоненты в папке Components доступной, когда корневое пространство имен приложения имеет значение WebApplication:For example, the following namespace makes components in a Components folder available when the app's root namespace is WebApplication:

@using WebApplication.Components

Интеграция компонентов в Razor Pages и приложения MVCIntegrate components into Razor Pages and MVC apps

Используйте компоненты с существующими Razor Pages и приложениями MVC.Use components with existing Razor Pages and MVC apps. Нет необходимости переписывать существующие страницы или представления для использования компонентов Razor.There's no need to rewrite existing pages or views to use Razor components. При отображении страницы или представления компоненты предварительно отображаются.When the page or view is rendered, components are prerendered at the same time.

Чтобы отобразить компонент из страницы или представления, используйте вспомогательный метод HTML RenderComponentAsync<TComponent>:To render a component from a page or view, use the RenderComponentAsync<TComponent> HTML helper method:

<div id="MyComponent">
    @(await Html.RenderComponentAsync<MyComponent>(RenderMode.ServerPrerendered))
</div>

Хотя страницы и представления могут использовать компоненты, наоборот это не так.While pages and views can use components, the converse isn't true. Компоненты не могут использовать сценарии представления и страницы, такие как частичные представления и разделы.Components can't use view- and page-specific scenarios, such as partial views and sections. Чтобы использовать логику из частичного представления в компоненте, разнесите логику частичного представления в компонент.To use logic from partial view in a component, factor out the partial view logic into a component.

Дополнительные сведения о подготовке компонентов к просмотру и управлении состоянием компонентов в Блазор Server Apps см. в статье ASP.NET Core моделей размещения Блазор.For more information on how components are rendered and component state is managed in Blazor Server apps, see the ASP.NET Core моделей размещения Блазор article.

Использование компонентовUse components

Компоненты могут включать другие компоненты, объявляя их с помощью синтаксиса HTML-элементов.Components can include other components by declaring them using HTML element syntax. Разметка для использования компонента выглядит как тег HTML с именем, соответствующем типу компонента.The markup for using a component looks like an HTML tag where the name of the tag is the component type.

Привязка атрибута чувствительна к регистру.Attribute binding is case sensitive. Например, @bind является допустимым, а @Bind является недопустимым.For example, @bind is valid, and @Bind is invalid.

Следующая разметка в index. Razor визуализирует экземпляр HeadingComponent:The following markup in Index.razor renders a HeadingComponent instance:

<HeadingComponent />

Components/хеадингкомпонент. Razor:Components/HeadingComponent.razor:

@using System.Globalization
@*
    The 'using' directive makes System.Globalization available to 
    the component. System.Globalization provides a method for 
    converting a string into title case (capitalizes the first 
    letter of every word in a string), which is used to convert a 
    a string into title case for a heading.
*@

@*
    Heading text is rendered by evaluating the _headingText field. 
    The font-style of the heading is rendered by evaluating the 
    _headingFontStyle field.
*@
<h1 style="font-style:@_headingFontStyle">@_headingText</h1>

<form>
    <div>
        @*
            A check box sets the font style and is bound to the 
            _italicsCheck field.
        *@
        <input type="checkbox" id="italicsCheck" 
               @bind="_italicsCheck" />
        <label class="form-check-label" 
            for="italicsCheck">Use italics</label>
    </div>

    @*
        When the form is submitted, the onclick event executes 
        the UpdateHeading method.
    *@
    <button type="button" class="btn btn-primary" @onclick="UpdateHeading">
        Update heading
    </button>
</form>

@code {
    private static TextInfo _tinfo = CultureInfo.CurrentCulture.TextInfo;
    private string _headingText = 
        _tinfo.ToTitleCase("welcome to blazor!");
    private string _headingFontStyle = "normal";
    private bool _italicsCheck = false;

    // When UpdateHeading is executed, _italicsCheck determines 
    // the value of _headingFontStyle to set the font style of the 
    // heading.
    public void UpdateHeading()
    {
        _headingFontStyle = _italicsCheck ? "italic" : "normal";
    }
}

Если компонент содержит HTML-элемент с первой буквой в верхнем регистре, который не соответствует имени компонента, выдается предупреждение, указывающее, что элемент имеет непредвиденное имя.If a component contains an HTML element with an uppercase first letter that doesn't match a component name, a warning is emitted indicating that the element has an unexpected name. Добавление оператора @using для пространства имен компонента делает компонент доступным, что приводит к удалению предупреждения.Adding an @using statement for the component's namespace makes the component available, which removes the warning.

Параметры компонентовComponent parameters

Компоненты могут иметь Параметры компонента, которые определяются с помощью общедоступных свойств класса Component с атрибутом [Parameter].Components can have component parameters, which are defined using public properties on the component class with the [Parameter] attribute. Используйте атрибуты, чтобы указать аргументы для компонента в разметке.Use attributes to specify arguments for a component in markup.

Components/чилдкомпонент. Razor:Components/ChildComponent.razor:

<div class="panel panel-default">
    <div class="panel-heading">@Title</div>
    <div class="panel-body">@ChildContent</div>

    <button class="btn btn-primary" @onclick="OnClick">
        Trigger a Parent component method
    </button>
</div>

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

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

    [Parameter]
    public EventCallback<MouseEventArgs> OnClick { get; set; }
}

В следующем примере ParentComponent задает значение свойства Title для ChildComponent.In the following example, the ParentComponent sets the value of the Title property of the ChildComponent.

Pages/паренткомпонент. Razor:Pages/ParentComponent.razor:

@page "/ParentComponent"

<h1>Parent-child example</h1>

<ChildComponent Title="Panel Title from Parent"
                OnClick="@ShowMessage">
    Content of the child component is supplied
    by the parent component.
</ChildComponent>

<p><b>@messageText</b></p>

@code {
    private string messageText;

    private void ShowMessage(MouseEventArgs e)
    {
        messageText = "Blaze a new trail with Blazor!";
    }
}

Дочернее содержимоеChild content

Компоненты могут устанавливать содержимое другого компонента.Components can set the content of another component. Назначение компонента предоставляет содержимое между тегами, задающих принимающий компонент.The assigning component provides the content between the tags that specify the receiving component.

В следующем примере ChildComponent имеет свойство ChildContent, представляющее RenderFragment, которое представляет сегмент пользовательского интерфейса для отрисовки.In the following example, the ChildComponent has a ChildContent property that represents a RenderFragment, which represents a segment of UI to render. Значение ChildContent размещается в разметке компонента, где должно быть визуализировано содержимое.The value of ChildContent is positioned in the component's markup where the content should be rendered. Значение ChildContent получено от родительского компонента и отображается в panel-body панели начальной загрузки.The value of ChildContent is received from the parent component and rendered inside the Bootstrap panel's panel-body.

Components/чилдкомпонент. Razor:Components/ChildComponent.razor:

<div class="panel panel-default">
    <div class="panel-heading">@Title</div>
    <div class="panel-body">@ChildContent</div>

    <button class="btn btn-primary" @onclick="OnClick">
        Trigger a Parent component method
    </button>
</div>

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

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

    [Parameter]
    public EventCallback<MouseEventArgs> OnClick { get; set; }
}

Примечание

Свойство, получающее содержимое RenderFragment, должно иметь имя ChildContent по соглашению.The property receiving the RenderFragment content must be named ChildContent by convention.

Следующий ParentComponent может предоставить содержимое для отрисовки ChildComponent, поместив содержимое в теги <ChildComponent>.The following ParentComponent can provide content for rendering the ChildComponent by placing the content inside the <ChildComponent> tags.

Pages/паренткомпонент. Razor:Pages/ParentComponent.razor:

@page "/ParentComponent"

<h1>Parent-child example</h1>

<ChildComponent Title="Panel Title from Parent"
                OnClick="@ShowMessage">
    Content of the child component is supplied
    by the parent component.
</ChildComponent>

<p><b>@messageText</b></p>

@code {
    private string messageText;

    private void ShowMessage(MouseEventArgs e)
    {
        messageText = "Blaze a new trail with Blazor!";
    }
}

Атрибуты Сплаттинг и произвольные параметрыAttribute splatting and arbitrary parameters

Компоненты могут записывать и отображать дополнительные атрибуты в дополнение к объявленным параметрам компонента.Components can capture and render additional attributes in addition to the component's declared parameters. Дополнительные атрибуты могут быть записаны в словарь, а затем сплаттед на элемент при подготовке компонента к просмотру с помощью директивы Razor @attributes .Additional attributes can be captured in a dictionary and then splatted onto an element when the component is rendered using the @attributes Razor directive. Этот сценарий полезен при определении компонента, который создает элемент разметки, поддерживающий разнообразные настройки.This scenario is useful when defining a component that produces a markup element that supports a variety of customizations. Например, может быть утомительно определять атрибуты отдельно для <input>, который поддерживает много параметров.For example, it can be tedious to define attributes separately for an <input> that supports many parameters.

В следующем примере первый элемент <input> (id="useIndividualParams") использует параметры отдельных компонентов, а второй элемент <input> (id="useAttributesDict") использует атрибут Сплаттинг:In the following example, the first <input> element (id="useIndividualParams") uses individual component parameters, while the second <input> element (id="useAttributesDict") uses attribute splatting:

<input id="useIndividualParams"
       maxlength="@Maxlength"
       placeholder="@Placeholder"
       required="@Required"
       size="@Size" />

<input id="useAttributesDict"
       @attributes="InputAttributes" />

@code {
    [Parameter]
    public string Maxlength { get; set; } = "10";

    [Parameter]
    public string Placeholder { get; set; } = "Input placeholder text";

    [Parameter]
    public string Required { get; set; } = "required";

    [Parameter]
    public string Size { get; set; } = "50";

    [Parameter]
    public Dictionary<string, object> InputAttributes { get; set; } =
        new Dictionary<string, object>()
        {
            { "maxlength", "10" },
            { "placeholder", "Input placeholder text" },
            { "required", "required" },
            { "size", "50" }
        };
}

Тип параметра должен реализовывать IEnumerable<KeyValuePair<string, object>> со строковыми ключами.The type of the parameter must implement IEnumerable<KeyValuePair<string, object>> with string keys. Использование IReadOnlyDictionary<string, object> также является параметром в этом сценарии.Using IReadOnlyDictionary<string, object> is also an option in this scenario.

Отображаемые элементы <input>, использующие оба подхода, идентичны:The rendered <input> elements using both approaches is identical:

<input id="useIndividualParams"
       maxlength="10"
       placeholder="Input placeholder text"
       required="required"
       size="50">

<input id="useAttributesDict"
       maxlength="10"
       placeholder="Input placeholder text"
       required="required"
       size="50">

Чтобы принять произвольные атрибуты, определите параметр компонента с помощью атрибута [Parameter] со свойством CaptureUnmatchedValues, для которого задано значение true.To accept arbitrary attributes, define a component parameter using the [Parameter] attribute with the CaptureUnmatchedValues property set to true:

@code {
    [Parameter(CaptureUnmatchedValues = true)]
    public Dictionary<string, object> InputAttributes { get; set; }
}

Свойство CaptureUnmatchedValues в [Parameter] позволяет параметру сопоставлять все атрибуты, не соответствующие ни одному другому параметру.The CaptureUnmatchedValues property on [Parameter] allows the parameter to match all attributes that don't match any other parameter. Компонент может определять только один параметр с CaptureUnmatchedValues.A component can only define a single parameter with CaptureUnmatchedValues. Тип свойства, используемый с CaptureUnmatchedValues, должен быть назначен из Dictionary<string, object> со строковыми ключами.The property type used with CaptureUnmatchedValues must be assignable from Dictionary<string, object> with string keys. в этом сценарии также используются параметры IEnumerable<KeyValuePair<string, object>> или IReadOnlyDictionary<string, object>.IEnumerable<KeyValuePair<string, object>> or IReadOnlyDictionary<string, object> are also options in this scenario.

Расположение @attributes относительно положения атрибутов элемента важно.The position of @attributes relative to the position of element attributes is important. Когда @attributes сплаттед для элемента, атрибуты обрабатываются справа налево (последний — первый).When @attributes are splatted on the element, the attributes are processed from right to left (last to first). Рассмотрим следующий пример компонента, использующего компонент Child:Consider the following example of a component that consumes a Child component:

Паренткомпонент. Razor:ParentComponent.razor:

<ChildComponent extra="10" />

Чилдкомпонент. Razor:ChildComponent.razor:

<div @attributes="AdditionalAttributes" extra="5" />

[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object> AdditionalAttributes { get; set; }

Атрибут extra Child компонента установлен справа от @attributes.The Child component's extra attribute is set to the right of @attributes. <div>, отображаемый компонентом Parent, содержит extra="5" при передаче через дополнительный атрибут, так как атрибуты обрабатываются справа налево (последний — первый):The Parent component's rendered <div> contains extra="5" when passed through the additional attribute because the attributes are processed right to left (last to first):

<div extra="5" />

В следующем примере порядок extra и @attributes отменяется в <div>е Child компонента:In the following example, the order of extra and @attributes is reversed in the Child component's <div>:

Паренткомпонент. Razor:ParentComponent.razor:

<ChildComponent extra="10" />

Чилдкомпонент. Razor:ChildComponent.razor:

<div extra="5" @attributes="AdditionalAttributes" />

[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object> AdditionalAttributes { get; set; }

Отображаемые <div> в компоненте Parent содержат extra="10" при передаче через дополнительный атрибут:The rendered <div> in the Parent component contains extra="10" when passed through the additional attribute:

<div extra="10" />

привязка данных,Data binding

Привязка данных как к компонентам, так и к элементам DOM осуществляется с помощью атрибута @bind .Data binding to both components and DOM elements is accomplished with the @bind attribute. В следующем примере свойство CurrentValue привязывается к значению текстового поля:The following example binds a CurrentValue property to the text box's value:

<input @bind="CurrentValue" />

@code {
    private string CurrentValue { get; set; }
}

Когда текстовое поле теряет фокус, значение свойства обновляется.When the text box loses focus, the property's value is updated.

Текстовое поле обновляется в пользовательском интерфейсе только при подготовке компонента к просмотру, а не в ответ на изменение значения свойства.The text box is updated in the UI only when the component is rendered, not in response to changing the property's value. Поскольку компоненты отображаются после выполнения кода обработчика событий, обновления свойств обычно отражаются в пользовательском интерфейсе сразу после запуска обработчика событий.Since components render themselves after event handler code executes, property updates are usually reflected in the UI immediately after an event handler is triggered.

Использование @bind со свойством CurrentValue (<input @bind="CurrentValue" />) в основном эквивалентно следующему:Using @bind with the CurrentValue property (<input @bind="CurrentValue" />) is essentially equivalent to the following:

<input value="@CurrentValue"
    @onchange="@((ChangeEventArgs __e) => CurrentValue = 
        __e.Value.ToString())" />
        
@code {
    private string CurrentValue { get; set; }
}

При подготовке к просмотру компонента value элемента input берется из свойства CurrentValue.When the component is rendered, the value of the input element comes from the CurrentValue property. Когда пользователь вводит текст в текстовое поле и изменяет фокус элемента, возникает событие onchange, а свойству CurrentValue присваивается измененное значение.When the user types in the text box and changes element focus, the onchange event is fired and the CurrentValue property is set to the changed value. На практике создание кода более сложно, так как @bind обрабатывает случаи, когда выполняются преобразования типов.In reality, the code generation is more complex because @bind handles cases where type conversions are performed. В принципе, @bind связывает текущее значение выражения с атрибутом value и обрабатывает изменения с помощью зарегистрированного обработчика.In principle, @bind associates the current value of an expression with a value attribute and handles changes using the registered handler.

Помимо обработки событий onchange с синтаксисом @bind, свойство или поле можно привязать с помощью других событий, указав атрибут @bind-value с параметром event (@bind-value:event).In addition to handling onchange events with @bind syntax, a property or field can be bound using other events by specifying an @bind-value attribute with an event parameter (@bind-value:event). В следующем примере привязывается свойство CurrentValue для события oninput:The following example binds the CurrentValue property for the oninput event:

<input @bind-value="CurrentValue" @bind-value:event="oninput" />

@code {
    private string CurrentValue { get; set; }
}

В отличие от onchange, которое срабатывает при потере фокуса элементом, oninput срабатывает при изменении значения текстового поля.Unlike onchange, which fires when the element loses focus, oninput fires when the value of the text box changes.

Неанализируемые значенияUnparsable values

Когда пользователь предоставляет неинтерпретируемое значение для элемента с привязкой к данным, неанализируемое значение автоматически возвращается к предыдущему значению при срабатывании события привязки.When a user provides an unparsable value to a databound element, the unparsable value is automatically reverted to its previous value when the bind event is triggered.

Рассмотрим следующий сценарий.Consider the following scenario:

  • Элемент <input> привязан к типу int с начальным значением 123:An <input> element is bound to an int type with an initial value of 123:

    <input @bind="MyProperty" />
    
    @code {
        [Parameter]
        public int MyProperty { get; set; } = 123;
    }
    
  • Пользователь обновляет значение элемента на 123.45 на странице и изменяет фокус элемента.The user updates the value of the element to 123.45 in the page and changes the element focus.

В приведенном выше сценарии значение элемента возвращается к 123.In the preceding scenario, the element's value is reverted to 123. Если значение 123.45 отклоняется в пользу исходного значения 123, пользователь понимает, что их значение не принято.When the value 123.45 is rejected in favor of the original value of 123, the user understands that their value wasn't accepted.

По умолчанию привязка применяется к событию onchange элемента (@bind="{PROPERTY OR FIELD}").By default, binding applies to the element's onchange event (@bind="{PROPERTY OR FIELD}"). Чтобы задать другое событие, используйте @bind-value="{PROPERTY OR FIELD}" @bind-value:event={EVENT}.Use @bind-value="{PROPERTY OR FIELD}" @bind-value:event={EVENT} to set a different event. Для события oninput (@bind-value:event="oninput") переверсия происходит после любого нажатия клавиши, которая вводит неинтерпретируемое значение.For the oninput event (@bind-value:event="oninput"), the reversion occurs after any keystroke that introduces an unparsable value. При нацеливании на событие oninput с типом, привязанным к int, пользователю запрещено вводить . символ.When targeting the oninput event with an int-bound type, a user is prevented from typing a . character. Символ . немедленно удаляется, поэтому пользователь получает немедленный отзыв о том, что разрешены только целые числа.A . character is immediately removed, so the user receives immediate feedback that only whole numbers are permitted. Существуют сценарии, в которых возвращение значения события oninput не является идеальным решением, например, когда пользователю нужно удалить неинтерпретируемое значение <input>.There are scenarios where reverting the value on the oninput event isn't ideal, such as when the user should be allowed to clear an unparsable <input> value. К альтернативам относятся:Alternatives include:

  • Не используйте событие oninput.Don't use the oninput event. Используйте событие onchange по умолчанию (@bind="{PROPERTY OR FIELD}"), где недопустимое значение не возвращается, пока элемент не теряет фокус.Use the default onchange event (@bind="{PROPERTY OR FIELD}"), where an invalid value isn't reverted until the element loses focus.
  • Выполните привязку к типу, допускающему значение null, например int? или string, и предоставьте настраиваемую логику для обработки недопустимых записей.Bind to a nullable type, such as int? or string, and provide custom logic to handle invalid entries.
  • Используйте компонент проверки формы, например InputNumber или InputDate.Use a form validation component, such as InputNumber or InputDate. Компоненты проверки форм имеют встроенную поддержку для управления недопустимыми входными данными.Form validation components have built-in support to manage invalid inputs. Компоненты проверки формы:Form validation components:
    • Позволяет пользователю указать недопустимые входные данные и получить ошибки проверки для связанного EditContext.Permit the user to provide invalid input and receive validation errors on the associated EditContext.
    • Отображает ошибки проверки в пользовательском интерфейсе, не мешая пользователю вводить дополнительные данные из формы.Display validation errors in the UI without interfering with the user entering additional webform data.

ГлобализацияGlobalization

значения @bind форматируются для вывода и анализируются с использованием правил текущего языка и региональных параметров.@bind values are formatted for display and parsed using the current culture's rules.

Доступ к текущему языку и региональным параметрам можно получить из свойства System.Globalization.CultureInfo.CurrentCulture.The current culture can be accessed from the System.Globalization.CultureInfo.CurrentCulture property.

CultureInfo. InvariantCulture используется для следующих типов полей (<input type="{TYPE}" />):CultureInfo.InvariantCulture is used for the following field types (<input type="{TYPE}" />):

  • date
  • number

Предыдущие типы полей:The preceding field types:

  • Отображаются с использованием соответствующих правил форматирования на основе браузера.Are displayed using their appropriate browser-based formatting rules.
  • Не может содержать текст в свободной форме.Can't contain free-form text.
  • Предоставление характеристик взаимодействия с пользователем в зависимости от реализации браузера.Provide user interaction characteristics based on the browser's implementation.

Следующие типы полей имеют особые требования к форматированию, которые в настоящее время не поддерживаются Блазор, так как они не поддерживаются всеми основными браузерами.The following field types have specific formatting requirements and aren't currently supported by Blazor because they aren't supported by all major browsers:

  • datetime-local
  • month
  • week

@bind поддерживает параметр @bind:culture, чтобы предоставить System.Globalization.CultureInfo для синтаксического анализа и форматирования значения.@bind supports the @bind:culture parameter to provide a System.Globalization.CultureInfo for parsing and formatting a value. Указание языка и региональных параметров не рекомендуется при использовании типов полей date и number.Specifying a culture isn't recommended when using the date and number field types. date и number имеют встроенную поддержку Блазор, которая предоставляет требуемый язык и региональные параметры.date and number have built-in Blazor support that provides the required culture.

Сведения о том, как задать язык и региональные параметры пользователя, см. в разделе Локализация .For information on how to set the user's culture, see the Localization section.

Строки форматаFormat strings

Привязка данных работает со строками формата DateTime с помощью @bind:format.Data binding works with DateTime format strings using @bind:format. Другие выражения форматирования, такие как денежные или числовые форматы, в настоящее время недоступны.Other format expressions, such as currency or number formats, aren't available at this time.

<input @bind="StartDate" @bind:format="yyyy-MM-dd" />

@code {
    [Parameter]
    public DateTime StartDate { get; set; } = new DateTime(2020, 1, 1);
}

В приведенном выше коде тип поля элемента <input> (type) по умолчанию имеет значение text.In the preceding code, the <input> element's field type (type) defaults to text. @bind:format поддерживается для привязки следующих типов .NET:@bind:format is supported for binding the following .NET types:

Атрибут @bind:format задает формат даты, применяемый к value элемента <input>.The @bind:format attribute specifies the date format to apply to the value of the <input> element. Формат также используется для анализа значения при возникновении события onchange.The format is also used to parse the value when an onchange event occurs.

Указание формата для типа поля date не рекомендуется, так как Блазор имеет встроенную поддержку для форматирования дат.Specifying a format for the date field type isn't recommended because Blazor has built-in support to format dates.

Параметры компонентаComponent parameters

Привязка распознает параметры компонента, где @bind-{property} может привязать значение свойства к нескольким компонентам.Binding recognizes component parameters, where @bind-{property} can bind a property value across components.

Следующий дочерний компонент (ChildComponent) имеет параметр компонента Year и обратный вызов YearChanged:The following child component (ChildComponent) has a Year component parameter and YearChanged callback:

<h2>Child Component</h2>

<p>Year: @Year</p>

@code {
    [Parameter]
    public int Year { get; set; }

    [Parameter]
    public EventCallback<int> YearChanged { get; set; }
}

EventCallback<T> объясняется в разделе вложенный EventCallback .EventCallback<T> is explained in the EventCallback section.

Следующий родительский компонент использует ChildComponent и привязывает параметр ParentYear от родительского к параметру Year в дочернем компоненте:The following parent component uses ChildComponent and binds the ParentYear parameter from the parent to the Year parameter on the child component:

@page "/ParentComponent"

<h1>Parent Component</h1>

<p>ParentYear: @ParentYear</p>

<ChildComponent @bind-Year="ParentYear" />

<button class="btn btn-primary" @onclick="ChangeTheYear">
    Change Year to 1986
</button>

@code {
    [Parameter]
    public int ParentYear { get; set; } = 1978;

    private void ChangeTheYear()
    {
        ParentYear = 1986;
    }
}

При загрузке ParentComponent создается следующая разметка:Loading the ParentComponent produces the following markup:

<h1>Parent Component</h1>

<p>ParentYear: 1978</p>

<h2>Child Component</h2>

<p>Year: 1978</p>

Если значение свойства ParentYear было изменено нажатием кнопки в ParentComponent, то обновляется свойство Year объекта ChildComponent.If the value of the ParentYear property is changed by selecting the button in the ParentComponent, the Year property of the ChildComponent is updated. Новое значение Year отображается в пользовательском интерфейсе при повторной отрисовке ParentComponent:The new value of Year is rendered in the UI when the ParentComponent is rerendered:

<h1>Parent Component</h1>

<p>ParentYear: 1986</p>

<h2>Child Component</h2>

<p>Year: 1986</p>

Параметр Year доступен для привязки, так как имеет сопутствующее событие YearChanged, соответствующее типу параметра Year.The Year parameter is bindable because it has a companion YearChanged event that matches the type of the Year parameter.

По соглашению <ChildComponent @bind-Year="ParentYear" /> по сути является аналогом записи:By convention, <ChildComponent @bind-Year="ParentYear" /> is essentially equivalent to writing:

<ChildComponent @bind-Year="ParentYear" @bind-Year:event="YearChanged" />

Как правило, свойство может быть привязано к соответствующему обработчику событий с помощью атрибута @bind-property:event.In general, a property can be bound to a corresponding event handler using @bind-property:event attribute. Например, свойство MyProp можно привязать к MyEventHandler с помощью следующих двух атрибутов:For example, the property MyProp can be bound to MyEventHandler using the following two attributes:

<MyComponent @bind-MyProp="MyValue" @bind-MyProp:event="MyEventHandler" />

Обработка событийEvent handling

Компоненты Razor предоставляют функции обработки событий.Razor components provide event handling features. Для атрибута элемента HTML с именем on{event} (например, onclick и onsubmit) со значением, определяемым делегатом, компоненты Razor рассматривают значение атрибута как обработчик событий.For an HTML element attribute named on{event} (for example, onclick and onsubmit) with a delegate-typed value, Razor components treats the attribute's value as an event handler. Имя атрибута всегда отформатировано @on {Event}.The attribute's name is always formatted @on{event}.

Следующий код вызывает метод UpdateHeading, когда в пользовательском интерфейсе выбрана кнопка:The following code calls the UpdateHeading method when the button is selected in the UI:

<button class="btn btn-primary" @onclick="UpdateHeading">
    Update heading
</button>

@code {
    private void UpdateHeading(MouseEventArgs e)
    {
        ...
    }
}

Следующий код вызывает метод CheckChanged, когда флажок изменяется в пользовательском интерфейсе:The following code calls the CheckChanged method when the check box is changed in the UI:

<input type="checkbox" class="form-check-input" @onchange="CheckChanged" />

@code {
    private void CheckChanged()
    {
        ...
    }
}

Обработчики событий также могут быть асинхронными и возвращать Task.Event handlers can also be asynchronous and return a Task. Нет необходимости вручную вызывать StateHasChanged().There's no need to manually call StateHasChanged(). Исключения регистрируются при их возникновении.Exceptions are logged when they occur.

В следующем примере UpdateHeading вызывается асинхронно при выборе этой кнопки:In the following example, UpdateHeading is called asynchronously when the button is selected:

<button class="btn btn-primary" @onclick="UpdateHeading">
    Update heading
</button>

@code {
    private async Task UpdateHeading(MouseEventArgs e)
    {
        ...
    }
}

Типы аргументов событийEvent argument types

Для некоторых событий разрешены типы аргументов событий.For some events, event argument types are permitted. Если доступ к одному из этих типов событий не нужен, в вызове метода это не требуется.If access to one of these event types isn't necessary, it isn't required in the method call.

Поддерживаемые EventArgs приведены в следующей таблице.Supported EventArgs are shown in the following table.

событиеEvent ClassClass
буфер обменаClipboard ClipboardEventArgs
ПереместитьDrag DragEventArgsDataTransfer и DataTransferItem содержат перетаскиваемые данные элемента.DragEventArgsDataTransfer and DataTransferItem hold dragged item data.
ErrorError ErrorEventArgs
ФокусFocus FocusEventArgs – не включает поддержку relatedTarget.FocusEventArgs – Doesn't include support for relatedTarget.
Изменение<input><input> change ChangeEventArgs
КлавиатураKeyboard KeyboardEventArgs
МышьMouse MouseEventArgs
Указатель мышиMouse pointer PointerEventArgs
Колесо мышиMouse wheel WheelEventArgs
Ход выполненияProgress ProgressEventArgs
Сенсорные технологииTouch TouchEventArgsTouchPoint представляет одну точку контакта на сенсорном устройстве.TouchEventArgsTouchPoint represents a single contact point on a touch-sensitive device.

Сведения о свойствах и поведении событий, описанных в приведенной выше таблице, см. в разделе классы EventArgs в источнике ссылки (ветвь ASPNET/AspNetCore Release/3.0).For information on the properties and event handling behavior of the events in the preceding table, see EventArgs classes in the reference source (aspnet/AspNetCore release/3.0 branch).

Лямбда-выраженияLambda expressions

Лямбда-выражения также могут использоваться:Lambda expressions can also be used:

<button @onclick="@(e => Console.WriteLine("Hello, world!"))">Say hello</button>

Часто бывает удобно закрывать дополнительные значения, например при переборе набора элементов.It's often convenient to close over additional values, such as when iterating over a set of elements. В следующем примере создаются три кнопки, каждый из которых вызывает UpdateHeading, передавая аргумент события (MouseEventArgs) и номер кнопки (buttonNumber) при выборе в пользовательском интерфейсе:The following example creates three buttons, each of which calls UpdateHeading passing an event argument (MouseEventArgs) and its button number (buttonNumber) when selected in the UI:

<h2>@message</h2>

@for (var i = 1; i < 4; i++)
{
    var buttonNumber = i;

    <button class="btn btn-primary"
            @onclick="@(e => UpdateHeading(e, buttonNumber))">
        Button #@i
    </button>
}

@code {
    private string message = "Select a button to learn its position.";

    private void UpdateHeading(MouseEventArgs e, int buttonNumber)
    {
        message = $"You selected Button #{buttonNumber} at " +
            $"mouse position: {e.ClientX} X {e.ClientY}.";
    }
}

Примечание

Не используйте переменную цикла (i) в цикле for непосредственно в лямбда-выражении.Do not use the loop variable (i) in a for loop directly in a lambda expression. В противном случае одна и та же переменная используется всеми лямбда-выражениями, в результате чего значение i будет одинаковым для всех лямбда-выражений.Otherwise the same variable is used by all lambda expressions causing i's value to be the same in all lambdas. Всегда запишите свое значение в локальную переменную (buttonNumber в предыдущем примере), а затем используйте ее.Always capture its value in a local variable (buttonNumber in the preceding example) and then use it.

Вложенный EventCallbackEventCallback

Распространенным сценарием с вложенными компонентами является желание запускать метод родительского компонента при возникновении события дочернего компонента —for примере, когда событие onclick возникает в дочернем элементе.A common scenario with nested components is the desire to run a parent component's method when a child component event occurs—for example, when an onclick event occurs in the child. Чтобы обеспечить доступ к событиям по компонентам, используйте EventCallback.To expose events across components, use an EventCallback. Родительский компонент может назначить метод обратного вызова EventCallback дочернего компонента.A parent component can assign a callback method to a child component's EventCallback.

ChildComponent в примере приложения демонстрирует настройку обработчика onclickа кнопки на получение делегата EventCallback из ParentComponentобразца.The ChildComponent in the sample app demonstrates how a button's onclick handler is set up to receive an EventCallback delegate from the sample's ParentComponent. EventCallback вводится MouseEventArgs, который подходит для события onclick на периферийном устройстве.The EventCallback is typed with MouseEventArgs, which is appropriate for an onclick event from a peripheral device:

<div class="panel panel-default">
    <div class="panel-heading">@Title</div>
    <div class="panel-body">@ChildContent</div>

    <button class="btn btn-primary" @onclick="OnClick">
        Trigger a Parent component method
    </button>
</div>

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

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

    [Parameter]
    public EventCallback<MouseEventArgs> OnClick { get; set; }
}

ParentComponent устанавливает EventCallback<T> дочернего элемента в метод ShowMessage:The ParentComponent sets the child's EventCallback<T> to its ShowMessage method:

@page "/ParentComponent"

<h1>Parent-child example</h1>

<ChildComponent Title="Panel Title from Parent"
                OnClick="@ShowMessage">
    Content of the child component is supplied
    by the parent component.
</ChildComponent>

<p><b>@messageText</b></p>

@code {
    private string messageText;

    private void ShowMessage(MouseEventArgs e)
    {
        messageText = "Blaze a new trail with Blazor!";
    }
}

При выборе кнопки в ChildComponent:When the button is selected in the ChildComponent:

  • Вызывается метод ParentComponent ShowMessage.The ParentComponent's ShowMessage method is called. messageText обновляется и отображается в ParentComponent.messageText is updated and displayed in the ParentComponent.
  • Вызов StateHasChanged не требуется в методе обратного вызова (ShowMessage).A call to StateHasChanged isn't required in the callback's method (ShowMessage). StateHasChanged вызывается автоматически для повторной визуализации ParentComponent, как и дочерние события. компонент активируется в обработчиках событий, которые выполняются внутри дочернего элемента.StateHasChanged is called automatically to rerender the ParentComponent, just as child events trigger component rerendering in event handlers that execute within the child.

EventCallback и EventCallback<T> допускают использование асинхронных делегатов.EventCallback and EventCallback<T> permit asynchronous delegates. EventCallback<T> является строго типизированным и требует определенного типа аргумента.EventCallback<T> is strongly typed and requires a specific argument type. EventCallback слабо типизирован и допускает любой тип аргумента.EventCallback is weakly typed and allows any argument type.

<p><b>@messageText</b></p>

@{ var message = "Default Text"; }

<ChildComponent 
    OnClick="@(async () => { await Task.Yield(); messageText = "Blaze It!"; })" />

@code {
    private string messageText;
}

Вызовите EventCallback или EventCallback<T> с InvokeAsync и ожидайте Task:Invoke an EventCallback or EventCallback<T> with InvokeAsync and await the Task:

await callback.InvokeAsync(arg);

Используйте EventCallback и EventCallback<T> для обработки событий и параметров компонента привязки.Use EventCallback and EventCallback<T> for event handling and binding component parameters.

Предпочитать строго типизированный EventCallback<T> на EventCallback.Prefer the strongly typed EventCallback<T> over EventCallback. EventCallback<T> обеспечивает лучшую реакцию на ошибки для пользователей компонента.EventCallback<T> provides better error feedback to users of the component. Как и в случае с другими обработчиками событий пользовательского интерфейса, указание параметра события является необязательным.Similar to other UI event handlers, specifying the event parameter is optional. Используйте EventCallback, если не передается значение обратного вызова.Use EventCallback when there's no value passed to the callback.

Привязка к цепочкеChained bind

Распространенным сценарием является привязка привязанного к данным параметра к элементу страницы в выходных данных компонента.A common scenario is chaining a data-bound parameter to a page element in the component's output. Этот сценарий называется связанной привязкой, так как несколько уровней привязки происходят одновременно.This scenario is called a chained bind because multiple levels of binding occur simultaneously.

Связанную с цепочкой привязку нельзя реализовать с помощью синтаксиса @bind в элементе страницы.A chained bind can't be implemented with @bind syntax in the page's element. Обработчик событий и значение должны быть указаны отдельно.The event handler and value must be specified separately. Однако родительский компонент может использовать синтаксис @bind с параметром компонента.A parent component, however, can use @bind syntax with the component's parameter.

Следующий компонент PasswordField (пассвордфиелд. Razor):The following PasswordField component (PasswordField.razor):

  • Задает значение элемента <input> для свойства Password.Sets an <input> element's value to a Password property.
  • Предоставляет изменения свойства Password в родительский компонент с помощью вложенный EventCallback.Exposes changes of the Password property to a parent component with an EventCallback.
Password: 

<input @oninput="OnPasswordChanged" 
       required 
       type="@(showPassword ? "text" : "password")" 
       value="@Password" />

<button class="btn btn-primary" @onclick="ToggleShowPassword">
    Show password
</button>

@code {
    private bool showPassword;

    [Parameter]
    public string Password { get; set; }

    [Parameter]
    public EventCallback<string> PasswordChanged { get; set; }

    private Task OnPasswordChanged(ChangeEventArgs e)
    {
        Password = e.Value.ToString();

        return PasswordChanged.InvokeAsync(Password);
    }

    private void ToggleShowPassword()
    {
        showPassword = !showPassword;
    }
}

Компонент PasswordField используется в другом компоненте:The PasswordField component is used in another component:

<PasswordField @bind-Password="password" />

@code {
    private string password;
}

Для выполнения проверок или перехвата ошибок в пароле в предыдущем примере:To perform checks or trap errors on the password in the preceding example:

  • Создайте резервное поле для Password (password в следующем примере кода).Create a backing field for Password (password in the following example code).
  • Выполните проверки или ошибки ловушек в методе задания Password.Perform the checks or trap errors in the Password setter.

В следующем примере представлена немедленная реакция пользователя, если в значении пароля используется пробел:The following example provides immediate feedback to the user if a space is used in the password's value:

Password: 

<input @oninput="OnPasswordChanged" 
       required 
       type="@(showPassword ? "text" : "password")" 
       value="@Password" />

<button class="btn btn-primary" @onclick="ToggleShowPassword">
    Show password
</button>

<span class="text-danger">@validationMessage</span>

@code {
    private bool showPassword;
    private string password;
    private string validationMessage;

    [Parameter]
    public string Password
    {
        get { return password ?? string.Empty; }
        set
        {
            if (password != value)
            {
                if (value.Contains(' '))
                {
                    validationMessage = "Spaces not allowed!";
                }
                else
                {
                    password = value;
                    validationMessage = string.Empty;
                }
            }
        }
    }

    [Parameter]
    public EventCallback<string> PasswordChanged { get; set; }

    private Task OnPasswordChanged(ChangeEventArgs e)
    {
        Password = e.Value.ToString();

        return PasswordChanged.InvokeAsync(Password);
    }

    private void ToggleShowPassword()
    {
        showPassword = !showPassword;
    }
}

Запись ссылок на компонентыCapture references to components

Ссылки на компоненты предоставляют способ ссылки на экземпляр компонента, чтобы можно было выполнять команды для этого экземпляра, например Show или Reset.Component references provide a way to reference a component instance so that you can issue commands to that instance, such as Show or Reset. Чтобы записать ссылку на компонент, сделайте следующее:To capture a component reference:

  • Добавьте атрибут @ref к дочернему компоненту.Add an @ref attribute to the child component.
  • Определите поле с тем же типом, что и у дочернего компонента.Define a field with the same type as the child component.
<MyLoginDialog @ref="loginDialog" ... />

@code {
    private MyLoginDialog loginDialog;

    private void OnSomething()
    {
        loginDialog.Show();
    }
}

При подготовке компонента к просмотру поле loginDialog заполняется экземпляром дочернего компонента MyLoginDialog.When the component is rendered, the loginDialog field is populated with the MyLoginDialog child component instance. Затем можно вызывать методы .NET в экземпляре компонента.You can then invoke .NET methods on the component instance.

Важно!

Переменная loginDialog заполняется только после подготовки компонента, а ее выходные данные включают элемент MyLoginDialog.The loginDialog variable is only populated after the component is rendered and its output includes the MyLoginDialog element. До этого момента нет никаких ссылок на.Until that point, there's nothing to reference. Чтобы управлять ссылками на компоненты после завершения подготовки компонента к просмотру, используйте методы онафтеррендерасинк или онафтеррендер.To manipulate components references after the component has finished rendering, use the OnAfterRenderAsync or OnAfterRender methods.

При захвате ссылок на компоненты используется аналогичный синтаксис для записи ссылок на элементы, но это не функция взаимодействия JavaScript .While capturing component references use a similar syntax to capturing element references, it isn't a JavaScript interop feature. Ссылки на компоненты не передаются в код JavaScript —they используются только в коде .NET.Component references aren't passed to JavaScript code—they're only used in .NET code.

Примечание

Не используйте ссылки на компоненты для изменения состояния дочерних компонентов.Do not use component references to mutate the state of child components. Вместо этого используйте обычные декларативные параметры для передачи данных дочерним компонентам.Instead, use normal declarative parameters to pass data to child components. Использование обычных декларативных параметров приводит к тому, что дочерние компоненты автоматически визуализируются в нужное время.Use of normal declarative parameters result in child components that rerender at the correct times automatically.

Вызывать методы компонента извне для обновления состоянияInvoke component methods externally to update state

Блазор использует SynchronizationContext для принудительного применения одного логического потока выполнения.Blazor uses a SynchronizationContext to enforce a single logical thread of execution. Методы жизненного цикла компонента и любые обратные вызовы событий, вызванные Блазор, выполняются на этом SynchronizationContext.A component's lifecycle methods and any event callbacks that are raised by Blazor are executed on this SynchronizationContext. В случае, если компонент должен быть обновлен на основе внешнего события, такого как таймер или другие уведомления, используйте метод InvokeAsync, который будет отправляться в Блазор SynchronizationContext.In the event a component must be updated based on an external event, such as a timer or other notifications, use the InvokeAsync method, which will dispatch to Blazor's SynchronizationContext.

Например, рассмотрим службу уведомления , которая может уведомлять любой компонент, выполняющий прослушивание, в обновленном состоянии:For example, consider a notifier service that can notify any listening component of the updated state:

public class NotifierService
{
    // Can be called from anywhere
    public async Task Update(string key, int value)
    {
        if (Notify != null)
        {
            await Notify.Invoke(key, value);
        }
    }

    public event Func<string, int, Task> Notify;
}

Использование NotifierService для обновления компонента:Usage of the NotifierService to update a component:

@page "/"
@inject NotifierService Notifier
@implements IDisposable

<p>Last update: @lastNotification.key = @lastNotification.value</p>

@code {
    private (string key, int value) lastNotification;

    protected override void OnInitialized()
    {
        Notifier.Notify += OnNotify;
    }

    public async Task OnNotify(string key, int value)
    {
        await InvokeAsync(() =>
        {
            lastNotification = (key, value);
            StateHasChanged();
        });
    }

    public void Dispose()
    {
        Notifier.Notify -= OnNotify;
    }
}

В предыдущем примере NotifierService вызывает метод OnNotify компонента за пределами Блазор SynchronizationContext.In the preceding example, NotifierService invokes the component's OnNotify method outside of Blazor's SynchronizationContext. InvokeAsync используется для переключения на правильный контекст и постановку в очередь визуализации.InvokeAsync is used to switch to the correct context and queue a render.

Использование @key для управления сохранением элементов и компонентовUse @key to control the preservation of elements and components

При последующем изменении списка элементов или компонентов, а также элементов или компонентов алгоритм сравнения Блазор должен решить, какие из предыдущих элементов или компонентов могут быть сохранены и как объекты модели должны сопоставляться с ними.When rendering a list of elements or components and the elements or components subsequently change, Blazor's diffing algorithm must decide which of the previous elements or components can be retained and how model objects should map to them. Обычно этот процесс выполняется автоматически и его можно игнорировать, но в некоторых случаях может потребоваться управление процессом.Normally, this process is automatic and can be ignored, but there are cases where you may want to control the process.

Рассмотрим следующий пример.Consider the following example:

@foreach (var person in People)
{
    <DetailsEditor Details="person.Details" />
}

@code {
    [Parameter]
    public IEnumerable<Person> People { get; set; }
}

Содержимое коллекции People может изменяться при вставке, удалении или повторном упорядочении записей.The contents of the People collection may change with inserted, deleted, or re-ordered entries. Когда компонент перерисовывается, компонент <DetailsEditor> может измениться на получение различных значений параметров Details.When the component rerenders, the <DetailsEditor> component may change to receive different Details parameter values. Это может привести к более сложной визуализации, чем ожидалось.This may cause more complex rerendering than expected. В некоторых случаях перерисовка может привести к появлению видимых различий в поведении, таких как потеря фокуса элементов.In some cases, rerendering can lead to visible behavior differences, such as lost element focus.

Процесс сопоставления можно контролировать с помощью атрибута директивы @key.The mapping process can be controlled with the @key directive attribute. @key приводит к тому, что алгоритм сравнения гарантирует сохранение элементов или компонентов на основе значения ключа:@key causes the diffing algorithm to guarantee preservation of elements or components based on the key's value:

@foreach (var person in People)
{
    <DetailsEditor @key="person" Details="person.Details" />
}

@code {
    [Parameter]
    public IEnumerable<Person> People { get; set; }
}

При изменении коллекции People алгоритм сравнения удерживает связь между экземплярами <DetailsEditor> и экземплярами person:When the People collection changes, the diffing algorithm retains the association between <DetailsEditor> instances and person instances:

  • Если Person удаляется из списка People, из пользовательского интерфейса удаляется только соответствующий экземпляр <DetailsEditor>.If a Person is deleted from the People list, only the corresponding <DetailsEditor> instance is removed from the UI. Другие экземпляры остаются без изменений.Other instances are left unchanged.
  • Если в некоторой позиции в списке вставляется Person, то в соответствующей позиции вставляется один новый экземпляр <DetailsEditor>.If a Person is inserted at some position in the list, one new <DetailsEditor> instance is inserted at that corresponding position. Другие экземпляры остаются без изменений.Other instances are left unchanged.
  • При повторном упорядочении записей Person соответствующие экземпляры <DetailsEditor> сохраняются и повторно упорядочиваются в пользовательском интерфейсе.If Person entries are re-ordered, the corresponding <DetailsEditor> instances are preserved and re-ordered in the UI.

В некоторых сценариях использование @key уменьшает сложность повторного отображения и позволяет избежать потенциальных проблем с элементами, изменяющимися с отслеживанием состояния DOM, например положением фокуса.In some scenarios, use of @key minimizes the complexity of rerendering and avoids potential issues with stateful parts of the DOM changing, such as focus position.

Важно!

Ключи являются локальными для каждого элемента или компонента контейнера.Keys are local to each container element or component. Ключи не сравниваются глобально по всему документу.Keys aren't compared globally across the document.

Когда следует использовать @keyWhen to use @key

Как правило, имеет смысл использовать @key при подготовке списка (например, в блоке @foreach), а подходящее значение — для определения @key.Typically, it makes sense to use @key whenever a list is rendered (for example, in a @foreach block) and a suitable value exists to define the @key.

Можно также использовать @key, чтобы предотвратить сохранение Блазор элемента или поддерева компонента при изменении объекта.You can also use @key to prevent Blazor from preserving an element or component subtree when an object changes:

<div @key="currentPerson">
    ... content that depends on currentPerson ...
</div>

Если @currentPerson меняется, директива атрибута @key заставляет Блазор отбросить весь <div> и его потомков и перестроить поддерево в пользовательском интерфейсе с помощью новых элементов и компонентов.If @currentPerson changes, the @key attribute directive forces Blazor to discard the entire <div> and its descendants and rebuild the subtree within the UI with new elements and components. Это может быть полезно, если необходимо гарантировать, что при изменении @currentPerson состояние пользовательского интерфейса не сохраняется.This can be useful if you need to guarantee that no UI state is preserved when @currentPerson changes.

Когда не следует использовать @keyWhen not to use @key

При использовании сравнения с @key возникают затраты на производительность.There's a performance cost when diffing with @key. Затраты на производительность не велики, но при управлении правилами сохранения элементов или компонентов вместо них следует указывать только @key.The performance cost isn't large, but only specify @key if controlling the element or component preservation rules benefit the app.

Даже если @key не используется, Блазор сохраняет дочерние элементы и экземпляры компонентов насколько это возможно.Even if @key isn't used, Blazor preserves child element and component instances as much as possible. Единственным преимуществом использования @key является управление тем, как экземпляры модели сопоставляются с сохраненными экземплярами компонента, а не алгоритмом сравнения, который выбирает сопоставление.The only advantage to using @key is control over how model instances are mapped to the preserved component instances, instead of the diffing algorithm selecting the mapping.

Какие значения следует использовать для @keyWhat values to use for @key

Как правило, имеет смысл указать один из следующих видов значений для @key:Generally, it makes sense to supply one of the following kinds of value for @key:

  • Экземпляры объектов Model (например, экземпляр Person, как в предыдущем примере).Model object instances (for example, a Person instance as in the earlier example). Это гарантирует сохранение на основе равенства ссылок на объекты.This ensures preservation based on object reference equality.
  • Уникальные идентификаторы (например, значения первичного ключа типа int, string или Guid).Unique identifiers (for example, primary key values of type int, string, or Guid).

Убедитесь, что значения, используемые для @key, не конфликтуют.Ensure that values used for @key don't clash. Если конфликтные значения обнаруживаются в одном родительском элементе, Блазор создает исключение, поскольку оно не может детерминированно сопоставлять старые элементы или компоненты с новыми элементами или компонентами.If clashing values are detected within the same parent element, Blazor throws an exception because it can't deterministically map old elements or components to new elements or components. Используйте только уникальные значения, такие как экземпляры объекта или значения первичного ключа.Only use distinct values, such as object instances or primary key values.

Методы жизненного циклаLifecycle methods

OnInitializedAsync и OnInitialized выполнить код для инициализации компонента.OnInitializedAsync and OnInitialized execute code to initialize the component. Для выполнения асинхронной операции используйте OnInitializedAsync и ключевое слово await для операции:To perform an asynchronous operation, use OnInitializedAsync and the await keyword on the operation:

protected override async Task OnInitializedAsync()
{
    await ...
}

Примечание

Асинхронная работа во время инициализации компонента должна происходить во время события жизненного цикла OnInitializedAsync.Asynchronous work during component initialization must occur during the OnInitializedAsync lifecycle event.

Для синхронной операции используйте OnInitialized:For a synchronous operation, use OnInitialized:

protected override void OnInitialized()
{
    ...
}

OnParametersSetAsync и OnParametersSet вызываются, когда компонент получил параметры от родительского объекта и значения присваиваются свойствам.OnParametersSetAsync and OnParametersSet are called when a component has received parameters from its parent and the values are assigned to properties. Эти методы выполняются после инициализации компонента и при каждом отображении компонента:These methods are executed after component initialization and each time the component is rendered:

protected override async Task OnParametersSetAsync()
{
    await ...
}

Примечание

Асинхронная работа при применении параметров и значений свойств должна происходить во время события жизненного цикла OnParametersSetAsync.Asynchronous work when applying parameters and property values must occur during the OnParametersSetAsync lifecycle event.

protected override void OnParametersSet()
{
    ...
}

OnAfterRenderAsync и OnAfterRender вызываются после завершения подготовки компонента к просмотру.OnAfterRenderAsync and OnAfterRender are called after a component has finished rendering. В этот момент заполнены ссылки на элементы и компоненты.Element and component references are populated at this point. Используйте этот этап для выполнения дополнительных шагов инициализации с помощью готового к просмотру содержимого, например для активации сторонних библиотек JavaScript, которые работают с визуализированными элементами DOM.Use this stage to perform additional initialization steps using the rendered content, such as activating third-party JavaScript libraries that operate on the rendered DOM elements.

OnAfterRender не вызывается при предварительной отрисовке на сервере.OnAfterRender isn't called when prerendering on the server.

Параметр firstRender для OnAfterRenderAsync и OnAfterRender:The firstRender parameter for OnAfterRenderAsync and OnAfterRender is:

  • Задайте значение true при первом вызове экземпляра компонента.Set to true the first time that the component instance is invoked.
  • Гарантирует, что работа по инициализации выполняется только один раз.Ensures that initialization work is only performed once.
protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (firstRender)
    {
        await ...
    }
}

Примечание

Асинхронная работа сразу после отрисовки должна произойти во время события жизненного цикла OnAfterRenderAsync.Asynchronous work immediately after rendering must occur during the OnAfterRenderAsync lifecycle event.

protected override void OnAfterRender(bool firstRender)
{
    if (firstRender)
    {
        ...
    }
}

Обработка незавершенных асинхронных действий при отрисовкеHandle incomplete async actions at render

Асинхронные действия, выполняемые в событиях жизненного цикла, могут не завершиться до подготовки компонента к просмотру.Asynchronous actions performed in lifecycle events may not have completed before the component is rendered. Объекты могут быть null или не заполнены данными во время выполнения метода жизненного цикла.Objects might be null or incompletely populated with data while the lifecycle method is executing. Предоставьте логику отрисовки для подтверждения инициализации объектов.Provide rendering logic to confirm that objects are initialized. Отрисовывает элементы пользовательского интерфейса заполнителя (например, сообщение загрузки), а объекты — null.Render placeholder UI elements (for example, a loading message) while objects are null.

В компоненте FetchData шаблонов Блазор OnInitializedAsync переопределяется в асичронаусли получения данных прогноза (forecasts).In the FetchData component of the Blazor templates, OnInitializedAsync is overridden to asychronously receive forecast data (forecasts). Если forecasts равно null, пользователю выводится сообщение о загрузке.When forecasts is null, a loading message is displayed to the user. После того, как Task возвращено OnInitializedAsync, компонент перерисовывается с обновленным состоянием.After the Task returned by OnInitializedAsync completes, the component is rerendered with the updated state.

Pages/FetchData.razor:Pages/FetchData.razor:

@page "/fetchdata"
@using MyBlazorApp.Data
@inject WeatherForecastService ForecastService

<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from a service.</p>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <!-- forecast data in table element content -->
    </table>
}

@code {
    private WeatherForecast[] forecasts;

    protected override async Task OnInitializedAsync()
    {
        forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
    }
}

Выполнить код до установки параметровExecute code before parameters are set

SetParameters можно переопределить для выполнения кода перед установкой параметров:SetParameters can be overridden to execute code before parameters are set:

public override void SetParameters(ParameterView parameters)
{
    ...

    base.SetParameters(parameters);
}

Если base.SetParameters не вызывается, Пользовательский код может интерпретировать значение входящих параметров любым необходимым образом.If base.SetParameters isn't invoked, the custom code can interpret the incoming parameters value in any way required. Например, входящие параметры не обязательно должны быть назначены свойствам класса.For example, the incoming parameters aren't required to be assigned to the properties on the class.

Отключить обновление пользовательского интерфейсаSuppress refreshing of the UI

ShouldRender можно переопределить, чтобы отключить обновление пользовательского интерфейса.ShouldRender can be overridden to suppress refreshing of the UI. Если реализация возвращает true, Пользовательский интерфейс обновляется.If the implementation returns true, the UI is refreshed. Даже при переопределении ShouldRender компонент всегда первоначально готовится к просмотру.Even if ShouldRender is overridden, the component is always initially rendered.

protected override bool ShouldRender()
{
    var renderUI = true;

    return renderUI;
}

Освобождение компонентов с помощью IDisposableComponent disposal with IDisposable

Если компонент реализует IDisposable, метод Dispose вызывается при удалении компонента из пользовательского интерфейса.If a component implements IDisposable, the Dispose method is called when the component is removed from the UI. Следующий компонент использует @implements IDisposable и метод Dispose:The following component uses @implements IDisposable and the Dispose method:

@using System
@implements IDisposable

...

@code {
    public void Dispose()
    {
        ...
    }
}

Примечание

Вызов StateHasChanged в Dispose не поддерживается.Calling StateHasChanged in Dispose isn't supported. StateHasChanged могут быть вызваны при разрыве модуля подготовки отчетов.StateHasChanged might be invoked as part of the renderer being torn down. Запрос обновлений пользовательского интерфейса на этом этапе не поддерживается.Requesting UI updates at that point isn't supported.

МаршрутизацияRouting

Маршрутизация в Блазор достигается путем предоставления шаблона маршрута для каждого доступного компонента в приложении.Routing in Blazor is achieved by providing a route template to each accessible component in the app.

При компиляции файла Razor с директивой @page создаваемому классу присваивается значение RouteAttribute, задающее шаблон маршрута.When a Razor file with an @page directive is compiled, the generated class is given a RouteAttribute specifying the route template. Во время выполнения маршрутизатор ищет классы компонентов с RouteAttribute и отображает, какой из компонентов имеет шаблон маршрута, соответствующий запрошенному URL-адресу.At runtime, the router looks for component classes with a RouteAttribute and renders whichever component has a route template that matches the requested URL.

К компоненту можно применить несколько шаблонов маршрутов.Multiple route templates can be applied to a component. Следующий компонент отвечает на запросы для /BlazorRoute и /DifferentBlazorRoute:The following component responds to requests for /BlazorRoute and /DifferentBlazorRoute:

@page "/BlazorRoute"
@page "/DifferentBlazorRoute"

<h1>Blazor routing</h1>

Параметры маршрутаRoute parameters

Компоненты могут принимать параметры маршрута из шаблона маршрута, указанного в директиве @page.Components can receive route parameters from the route template provided in the @page directive. Маршрутизатор использует параметры маршрута для заполнения соответствующих параметров компонента.The router uses route parameters to populate the corresponding component parameters.

Компонент параметра маршрута:Route Parameter component:

@page "/RouteParameter"
@page "/RouteParameter/{text}"

<h1>Blazor is @Text!</h1>

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

    protected override void OnInitialized()
    {
        Text = Text ?? "fantastic";
    }
}

Необязательные параметры не поддерживаются, поэтому в приведенном выше примере применяются две директивы @page.Optional parameters aren't supported, so two @page directives are applied in the example above. Первый позволяет переходить к компоненту без параметра.The first permits navigation to the component without a parameter. Вторая директива @page принимает параметр маршрута {text} и присваивает значение свойству Text.The second @page directive takes the {text} route parameter and assigns the value to the Text property.

Поддержка разделяемых классовPartial class support

Компоненты Razor создаются как разделяемые классы.Razor components are generated as partial classes. Компоненты Razor создаются с помощью любого из следующих подходов:Razor components are authored using either of the following approaches:

  • C#код определяется в @code блоке с разметкой HTML и кодом Razor в одном файле.C# code is defined in an @code block with HTML markup and Razor code in a single file. Шаблоны блазор определяют свои компоненты Razor с помощью этого подхода.Blazor templates define their Razor components using this approach.
  • C#код помещается в файл кода программной части, определенный как разделяемый класс.C# code is placed in a code-behind file defined as a partial class.

В следующем примере показан компонент Counter по умолчанию с блоком @code в приложении, созданном из шаблона Блазор.The following example shows the default Counter component with an @code block in an app generated from a Blazor template. Разметка HTML, код Razor и C# код находятся в одном файле:HTML markup, Razor code, and C# code are in the same file:

Counter. Razor:Counter.razor:

@page "/counter"

<h1>Counter</h1>

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

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    int currentCount = 0;

    void IncrementCount()
    {
        currentCount++;
    }
}

Counter компонент можно также создать с помощью файла кода программной части с разделяемым классом:The Counter component can also be created using a code-behind file with a partial class:

Counter. Razor:Counter.razor:

@page "/counter"

<h1>Counter</h1>

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

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

Counter.Razor.CS:Counter.razor.cs:

namespace BlazorApp.Pages
{
    public partial class Counter
    {
        int currentCount = 0;

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

Укажите базовый класс компонентаSpecify a component base class

Директиву @inherits можно использовать, чтобы указать базовый класс для компонента.The @inherits directive can be used to specify a base class for a component.

Пример приложения показывает, как компонент может наследовать базовый класс BlazorRocksBase для предоставления свойств и методов компонента.The sample app shows how a component can inherit a base class, BlazorRocksBase, to provide the component's properties and methods.

Pages/блазорроккс. Razor:Pages/BlazorRocks.razor:

@page "/BlazorRocks"
@inherits BlazorRocksBase

<h1>@BlazorRocksText</h1>

BlazorRocksBase.CS:BlazorRocksBase.cs:

using Microsoft.AspNetCore.Components;

namespace BlazorSample
{
    public class BlazorRocksBase : ComponentBase
    {
        public string BlazorRocksText { get; set; } = 
            "Blazor rocks the browser!";
    }
}

Базовый класс должен быть производным от ComponentBase.The base class should derive from ComponentBase.

Импорт компонентовImport components

Пространство имен компонента, созданного с помощью Razor, основано на (в порядке приоритета):The namespace of a component authored with Razor is based on (in priority order):

  • @namespace обозначение в разметке файла Razor ( . Razor) (@namespace BlazorSample.MyNamespace).@namespace designation in Razor file (.razor) markup (@namespace BlazorSample.MyNamespace).
  • RootNamespace проекта в файле проекта (<RootNamespace>BlazorSample</RootNamespace>).The project's RootNamespace in the project file (<RootNamespace>BlazorSample</RootNamespace>).
  • Имя проекта, полученное из имени файла проекта (CSPROJ), и путь из корневого каталога проекта к компоненту.The project name, taken from the project file's file name (.csproj), and the path from the project root to the component. Например, платформа разрешает {root проекта}/Пажес/индекс.Разор (блазорсампле. csproj) к пространству имен BlazorSample.Pages.For example, the framework resolves {PROJECT ROOT}/Pages/Index.razor (BlazorSample.csproj) to the namespace BlazorSample.Pages. Компоненты следуют C# правилам привязки имен.Components follow C# name binding rules. Для компонента Index в этом примере компоненты в области являются всеми компонентами:For the Index component in this example, the components in scope are all of the components:
    • В той же папке страницы.In the same folder, Pages.
    • Компоненты в корне проекта, которые не задают явно другое пространство имен.The components in the project's root that don't explicitly specify a different namespace.

Компоненты, определенные в другом пространстве имен, передаются в область с помощью директивы Razor @using .Components defined in a different namespace are brought into scope using Razor's @using directive.

Если другой компонент, NavMenu.razor, существует в папке блазорсампле/Shared/ Folder, компонент можно использовать в Index.razor со следующей инструкцией @using:If another component, NavMenu.razor, exists in the BlazorSample/Shared/ folder, the component can be used in Index.razor with the following @using statement:

@using BlazorSample.Shared

This is the Index page.

<NavMenu></NavMenu>

На компоненты также можно ссылаться с помощью полных имен, для которых не требуется директива @using :Components can also be referenced using their fully qualified names, which doesn't require the @using directive:

This is the Index page.

<BlazorSample.Shared.NavMenu></BlazorSample.Shared.NavMenu>

Примечание

Квалификация global:: не поддерживается.The global:: qualification isn't supported.

Импорт компонентов с псевдонимами using (например, @using Foo = Bar) не поддерживается.Importing components with aliased using statements (for example, @using Foo = Bar) isn't supported.

Частично определенные имена не поддерживаются.Partially qualified names aren't supported. Например, добавление @using BlazorSample и ссылка NavMenu.razor с помощью <Shared.NavMenu></Shared.NavMenu> не поддерживается.For example, adding @using BlazorSample and referencing NavMenu.razor with <Shared.NavMenu></Shared.NavMenu> isn't supported.

Атрибуты условного HTML-элементаConditional HTML element attributes

Атрибуты HTML-элемента условно подготавливаются в зависимости от значения .NET.HTML element attributes are conditionally rendered based on the .NET value. Если значение равно false или null, то атрибут не отображается.If the value is false or null, the attribute isn't rendered. Если значение равно true, то атрибут отображается в виде сворачивания.If the value is true, the attribute is rendered minimized.

В следующем примере IsCompleted определяет, отображается ли checked в разметке элемента:In the following example, IsCompleted determines if checked is rendered in the element's markup:

<input type="checkbox" checked="@IsCompleted" />

@code {
    [Parameter]
    public bool IsCompleted { get; set; }
}

Если IsCompleted равно true, флажок отображается следующим образом:If IsCompleted is true, the check box is rendered as:

<input type="checkbox" checked />

Если IsCompleted равно false, флажок отображается следующим образом:If IsCompleted is false, the check box is rendered as:

<input type="checkbox" />

Для получения дополнительной информации см. Справочник по синтаксису Razor для ASP.NET Core.For more information, see Справочник по синтаксису Razor для ASP.NET Core.

Предупреждение

Некоторые атрибуты HTML, такие как ARIA-Pressed, не работают должным образом, если тип .net — bool.Some HTML attributes, such as aria-pressed, don't function properly when the .NET type is a bool. В этих случаях используйте тип string вместо bool.In those cases, use a string type instead of a bool.

Необработанный HTMLRaw HTML

Строки обычно подготавливаются с помощью текстовых узлов DOM, что означает, что любая разметка, которую они могут содержать, игнорируется и обрабатывается как литеральный текст.Strings are normally rendered using DOM text nodes, which means that any markup they may contain is ignored and treated as literal text. Для отрисовки необработанного HTML-кода заключите содержимое HTML в значение MarkupString.To render raw HTML, wrap the HTML content in a MarkupString value. Значение анализируется как HTML или SVG и вставляется в модель DOM.The value is parsed as HTML or SVG and inserted into the DOM.

Предупреждение

Визуализация необработанного HTML-кода, созданного из любого ненадежного источника, является угрозой безопасности , и ее следует избегать.Rendering raw HTML constructed from any untrusted source is a security risk and should be avoided!

В следующем примере показано использование типа MarkupString для добавления блока статического HTML-содержимого в отображаемые выходные данные компонента.The following example shows using the MarkupString type to add a block of static HTML content to the rendered output of a component:

@((MarkupString)myMarkup)

@code {
    private string myMarkup = 
        "<p class='markup'>This is a <em>markup string</em>.</p>";
}

Шаблонные компонентыTemplated components

Шаблонные компоненты — это компоненты, принимающие один или несколько шаблонов пользовательского интерфейса в качестве параметров, которые затем можно использовать как часть логики отрисовки компонента.Templated components are components that accept one or more UI templates as parameters, which can then be used as part of the component's rendering logic. Шаблонные компоненты позволяют создавать более высокие компоненты более высокого уровня, чем обычные компоненты.Templated components allow you to author higher-level components that are more reusable than regular components. Ниже приведены несколько примеров.A couple of examples include:

  • Компонент таблицы, позволяющий пользователю указывать шаблоны для заголовков, строк и нижних колонтитулов таблицы.A table component that allows a user to specify templates for the table's header, rows, and footer.
  • Компонент списка, позволяющий пользователю указать шаблон для отрисовки элементов в списке.A list component that allows a user to specify a template for rendering items in a list.

Параметры шаблонаTemplate parameters

Шаблонный компонент определяется путем указания одного или нескольких параметров компонента типа RenderFragment или RenderFragment<T>.A templated component is defined by specifying one or more component parameters of type RenderFragment or RenderFragment<T>. Фрагмент отрисовки представляет сегмент пользовательского интерфейса для отрисовки.A render fragment represents a segment of UI to render. RenderFragment<T> принимает параметр типа, который может быть указан при вызове фрагмента прорисовки.RenderFragment<T> takes a type parameter that can be specified when the render fragment is invoked.

компонент TableTemplate:TableTemplate component:

@typeparam TItem

<table class="table">
    <thead>
        <tr>@TableHeader</tr>
    </thead>
    <tbody>
        @foreach (var item in Items)
        {
            <tr>@RowTemplate(item)</tr>
        }
    </tbody>
    <tfoot>
        <tr>@TableFooter</tr>
    </tfoot>
</table>

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

    [Parameter]
    public RenderFragment<TItem> RowTemplate { get; set; }

    [Parameter]
    public RenderFragment TableFooter { get; set; }

    [Parameter]
    public IReadOnlyList<TItem> Items { get; set; }
}

При использовании компонент-шаблона можно указать параметры шаблона с помощью дочерних элементов, совпадающих с именами параметров (TableHeader и RowTemplate в следующем примере):When using a templated component, the template parameters can be specified using child elements that match the names of the parameters (TableHeader and RowTemplate in the following example):

<TableTemplate Items="pets">
    <TableHeader>
        <th>ID</th>
        <th>Name</th>
    </TableHeader>
    <RowTemplate>
        <td>@context.PetId</td>
        <td>@context.Name</td>
    </RowTemplate>
</TableTemplate>

Параметры контекста шаблонаTemplate context parameters

Аргументы компонента типа RenderFragment<T>, переданные как элементы, имеют неявный параметр с именем context (например, из предыдущего примера кода, @context.PetId), но можно изменить имя параметра с помощью атрибута Context в дочернем элементе.Component arguments of type RenderFragment<T> passed as elements have an implicit parameter named context (for example from the preceding code sample, @context.PetId), but you can change the parameter name using the Context attribute on the child element. В следующем примере атрибуту Context элемента RowTemplate задается параметр pet:In the following example, the RowTemplate element's Context attribute specifies the pet parameter:

<TableTemplate Items="pets">
    <TableHeader>
        <th>ID</th>
        <th>Name</th>
    </TableHeader>
    <RowTemplate Context="pet">
        <td>@pet.PetId</td>
        <td>@pet.Name</td>
    </RowTemplate>
</TableTemplate>

Кроме того, можно указать атрибут Context в элементе Component.Alternatively, you can specify the Context attribute on the component element. Указанный атрибут Context применяется ко всем указанным параметрам шаблона.The specified Context attribute applies to all specified template parameters. Это может быть полезно, если необходимо указать имя параметра содержимого для неявного дочернего содержимого (без обертывания дочернего элемента).This can be useful when you want to specify the content parameter name for implicit child content (without any wrapping child element). В следующем примере атрибут Context отображается в элементе TableTemplate и применяется ко всем параметрам шаблона:In the following example, the Context attribute appears on the TableTemplate element and applies to all template parameters:

<TableTemplate Items="pets" Context="pet">
    <TableHeader>
        <th>ID</th>
        <th>Name</th>
    </TableHeader>
    <RowTemplate>
        <td>@pet.PetId</td>
        <td>@pet.Name</td>
    </RowTemplate>
</TableTemplate>

Универсальные типы компонентовGeneric-typed components

Шаблонные компоненты часто вводятся в универсальном виде.Templated components are often generically typed. Например, универсальный компонент ListViewTemplate можно использовать для отображения значений IEnumerable<T>.For example, a generic ListViewTemplate component can be used to render IEnumerable<T> values. Чтобы определить универсальный компонент, используйте директиву @typeparam для указания параметров типа:To define a generic component, use the @typeparam directive to specify type parameters:

@typeparam TItem

<ul>
    @foreach (var item in Items)
    {
        @ItemTemplate(item)
    }
</ul>

@code {
    [Parameter]
    public RenderFragment<TItem> ItemTemplate { get; set; }

    [Parameter]
    public IReadOnlyList<TItem> Items { get; set; }
}

При использовании компонентов универсальной типизации параметр типа выводится, если это возможно:When using generic-typed components, the type parameter is inferred if possible:

<ListViewTemplate Items="pets">
    <ItemTemplate Context="pet">
        <li>@pet.Name</li>
    </ItemTemplate>
</ListViewTemplate>

В противном случае параметр типа необходимо явно указать с помощью атрибута, совпадающего с именем параметра типа.Otherwise, the type parameter must be explicitly specified using an attribute that matches the name of the type parameter. В следующем примере TItem="Pet" указывает тип:In the following example, TItem="Pet" specifies the type:

<ListViewTemplate Items="pets" TItem="Pet">
    <ItemTemplate Context="pet">
        <li>@pet.Name</li>
    </ItemTemplate>
</ListViewTemplate>

Каскадные значения и параметрыCascading values and parameters

В некоторых сценариях неудобно передавать данные из компонента-предка в дочерний компонент с помощью параметров компонента, особенно при наличии нескольких слоев компонентов.In some scenarios, it's inconvenient to flow data from an ancestor component to a descendent component using component parameters, especially when there are several component layers. Каскадные значения и параметры позволяют решить эту проблему, предоставляя удобный способ, позволяющий компоненту-предку предоставить значение всем его дочерним компонентам.Cascading values and parameters solve this problem by providing a convenient way for an ancestor component to provide a value to all of its descendent components. Каскадные значения и параметры также предоставляют подход для координации компонентов.Cascading values and parameters also provide an approach for components to coordinate.

Пример темыTheme example

В следующем примере из примера приложения класс ThemeInfo указывает сведения о теме для перетекания иерархии компонентов таким образом, чтобы все кнопки в пределах данной части приложения совместно совпадали с тем же стилем.In the following example from the sample app, the ThemeInfo class specifies the theme information to flow down the component hierarchy so that all of the buttons within a given part of the app share the same style.

Уисемеклассес/themeinfo указывает расположение. CS:UIThemeClasses/ThemeInfo.cs:

public class ThemeInfo
{
    public string ButtonClass { get; set; }
}

Компонент-предок может предоставлять каскадное значение с помощью компонента каскадного значения.An ancestor component can provide a cascading value using the Cascading Value component. Компонент CascadingValue заключает в оболочку поддерево иерархии компонентов и предоставляет одно значение всем компонентам в этом поддереве.The CascadingValue component wraps a subtree of the component hierarchy and supplies a single value to all components within that subtree.

Например, в примере приложения указываются сведения о теме (ThemeInfo) в одном из макетов приложения в виде каскадного параметра для всех компонентов, составляющих тело макета свойства @Body.For example, the sample app specifies theme information (ThemeInfo) in one of the app's layouts as a cascading parameter for all components that make up the layout body of the @Body property. ButtonClass присвоено значение btn-success в компоненте макета.ButtonClass is assigned a value of btn-success in the layout component. Любой дочерний компонент может использовать это свойство через каскадный объект ThemeInfo.Any descendent component can consume this property through the ThemeInfo cascading object.

компонент CascadingValuesParametersLayout:CascadingValuesParametersLayout component:

@inherits LayoutComponentBase
@using BlazorSample.UIThemeClasses

<div class="container-fluid">
    <div class="row">
        <div class="col-sm-3">
            <NavMenu />
        </div>
        <div class="col-sm-9">
            <CascadingValue Value="theme">
                <div class="content px-4">
                    @Body
                </div>
            </CascadingValue>
        </div>
    </div>
</div>

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

Чтобы использовать каскадные значения, компоненты объявляют каскадные параметры с помощью атрибута [CascadingParameter].To make use of cascading values, components declare cascading parameters using the [CascadingParameter] attribute. Каскадные значения привязываются к каскадным параметрам по типу.Cascading values are bound to cascading parameters by type.

В примере приложения компонент CascadingValuesParametersTheme привязывает каскадное значение ThemeInfo к каскадному параметру.In the sample app, the CascadingValuesParametersTheme component binds the ThemeInfo cascading value to a cascading parameter. Параметр используется для задания класса CSS для одной из кнопок, отображаемых компонентом.The parameter is used to set the CSS class for one of the buttons displayed by the component.

компонент CascadingValuesParametersTheme:CascadingValuesParametersTheme component:

@page "/cascadingvaluesparameterstheme"
@layout CascadingValuesParametersLayout
@using BlazorSample.UIThemeClasses

<h1>Cascading Values & Parameters</h1>

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

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

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

@code {
    private int currentCount = 0;

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

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

Чтобы вычислить несколько значений одного типа в одном поддереве, укажите уникальную строку Name для каждого компонента CascadingValue и соответствующего CascadingParameter.To cascade multiple values of the same type within the same subtree, provide a unique Name string to each CascadingValue component and its corresponding CascadingParameter. В следующем примере два компонента CascadingValue разворачивают разные экземпляры MyCascadingType по имени:In the following example, two CascadingValue components cascade different instances of MyCascadingType by name:

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

@code {
    private MyCascadingType ParentCascadeParameter1;

    [Parameter]
    public MyCascadingType ParentCascadeParameter2 { get; set; }

    ...
}

В компоненте-потомках каскадные параметры получают значения из соответствующих каскадных значений в компоненте-предке по имени:In a descendant component, the cascaded parameters receive their values from the corresponding cascaded values in the ancestor component by name:

...

@code {
    [CascadingParameter(Name = "CascadeParam1")]
    protected MyCascadingType ChildCascadeParameter1 { get; set; }
    
    [CascadingParameter(Name = "CascadeParam2")]
    protected MyCascadingType ChildCascadeParameter2 { get; set; }
}

Пример ТабсетTabSet example

Каскадные параметры также позволяют компонентам работать в иерархии компонентов.Cascading parameters also enable components to collaborate across the component hierarchy. Например, рассмотрим следующий пример табсет в примере приложения.For example, consider the following TabSet example in the sample app.

В примере приложения имеется интерфейс ITab, который реализуется вкладками:The sample app has an ITab interface that tabs implement:

using Microsoft.AspNetCore.Components;

namespace BlazorSample.UIInterfaces
{
    public interface ITab
    {
        RenderFragment ChildContent { get; }
    }
}

Компонент CascadingValuesParametersTabSet использует компонент TabSet, который содержит несколько компонентов Tab:The CascadingValuesParametersTabSet component uses the TabSet component, which contains several Tab components:

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

        <label>
            <input type="checkbox" @bind="showThirdTab" />
            Toggle third tab
        </label>
    </Tab>
    <Tab Title="Second tab">
        <h4>The second tab says Hello World!</h4>
    </Tab>

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

Дочерние компоненты Tab не передаются в качестве параметров в TabSet.The child Tab components aren't explicitly passed as parameters to the TabSet. Вместо этого дочерние компоненты Tab являются частью дочернего содержимого TabSet.Instead, the child Tab components are part of the child content of the TabSet. Однако TabSet по-прежнему необходимо узнать о каждом компоненте Tab, чтобы он мог отобразить заголовки и активную вкладку. Чтобы обеспечить эту координацию, не требуя дополнительного кода, компонент TabSet может предоставить себя как каскадное значение , которое затем будет использоваться дочерними компонентами Tab.However, the TabSet still needs to know about each Tab component so that it can render the headers and the active tab. To enable this coordination without requiring additional code, the TabSet component can provide itself as a cascading value that is then picked up by the descendent Tab components.

компонент TabSet:TabSet component:

@using BlazorSample.UIInterfaces

<!-- Display the tab headers -->
<CascadingValue Value=this>
    <ul class="nav nav-tabs">
        @ChildContent
    </ul>
</CascadingValue>

<!-- Display body for only the active tab -->
<div class="nav-tabs-body p-4">
    @ActiveTab?.ChildContent
</div>

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

    public ITab ActiveTab { get; private set; }

    public void AddTab(ITab tab)
    {
        if (ActiveTab == null)
        {
            SetActivateTab(tab);
        }
    }

    public void RemoveTab(ITab tab)
    {
        if (ActiveTab == tab)
        {
            SetActivateTab(null);
        }
    }

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

Дочерние компоненты Tab захватывают содержащий TabSet как каскадный параметр, поэтому компоненты Tab добавляют себя в TabSet и координирует, какая вкладка активна.The descendent Tab components capture the containing TabSet as a cascading parameter, so the Tab components add themselves to the TabSet and coordinate on which tab is active.

компонент Tab:Tab component:

@using BlazorSample.UIInterfaces
@implements ITab

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

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

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

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

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

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

    private void Activate()
    {
        ContainerTabSet.SetActivateTab(this);
    }
}

Шаблоны RazorRazor templates

Фрагменты визуализации можно определить с помощью синтаксиса шаблона Razor.Render fragments can be defined using Razor template syntax. Шаблоны Razor позволяют определить фрагмент пользовательского интерфейса и принять следующий формат:Razor templates are a way to define a UI snippet and assume the following format:

@<{HTML tag}>...</{HTML tag}>

В следующем примере показано, как указать значения RenderFragment и RenderFragment<T> и визуализировать шаблоны непосредственно в компоненте.The following example illustrates how to specify RenderFragment and RenderFragment<T> values and render templates directly in a component. Фрагменты рендеринга также могут передаваться в качестве аргументов в шаблонные компоненты.Render fragments can also be passed as arguments to templated components.

@timeTemplate

@petTemplate(new Pet { Name = "Rex" })

@code {
    private RenderFragment timeTemplate = @<p>The time is @DateTime.Now.</p>;
    private RenderFragment<Pet> petTemplate = 
        (pet) => @<p>Your pet's name is @pet.Name.</p>;

    private class Pet
    {
        public string Name { get; set; }
    }
}

Отображаемые выходные данные предыдущего кода:Rendered output of the preceding code:

<p>The time is 10/04/2018 01:26:52.</p>

<p>Your pet's name is Rex.</p>

Логика Рендертрибуилдер вручнуюManual RenderTreeBuilder logic

Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder предоставляет методы для управления компонентами и элементами, включая создание компонентов вручную в C# коде.Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder provides methods for manipulating components and elements, including building components manually in C# code.

Примечание

Использование RenderTreeBuilder для создания компонентов является расширенным сценарием.Use of RenderTreeBuilder to create components is an advanced scenario. Неправильно сформированный компонент (например, незакрытый тег разметки) может привести к неопределенному поведению.A malformed component (for example, an unclosed markup tag) can result in undefined behavior.

Рассмотрим следующий компонент PetDetails, который можно вручную встроить в другой компонент: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; }
}

В следующем примере цикл в методе CreateComponent создает три компонента PetDetails.In the following example, the loop in the CreateComponent method generates three PetDetails components. При вызове методов RenderTreeBuilder для создания компонентов (OpenComponent и AddAttribute) порядковые номера представляют собой номера строк исходного кода.When calling RenderTreeBuilder methods to create the components (OpenComponent and AddAttribute), sequence numbers are source code line numbers. Алгоритм разницы Блазор зависит от порядковых номеров, соответствующих разным строкам кода, а не отдельных вызовов вызова.The Blazor difference algorithm relies on the sequence numbers corresponding to distinct lines of code, not distinct call invocations. При создании компонента с методами RenderTreeBuilder жестко указывайте аргументы для порядковых номеров.When creating a component with RenderTreeBuilder methods, hardcode the arguments for sequence numbers. Использование вычисления или счетчика для формирования порядкового номера может привести к снижению производительности.Using a calculation or counter to generate the sequence number can lead to poor performance. Дополнительные сведения см. в разделе порядковые номера относятся к разделу номера строк кода и порядок выполнения .For more information, see the Sequence numbers relate to code line numbers and not execution order section.

компонент BuiltContent:BuiltContent 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();
    }
}

! ! Типы в Microsoft.AspNetCore.Components.RenderTree позволяют обрабатывать результаты операций отрисовки.![WARNING] The types in Microsoft.AspNetCore.Components.RenderTree allow processing of the results of rendering operations. Это внутренние сведения о реализации Блазор Framework.These are internal details of the Blazor framework implementation. Эти типы следует считать нестабильными и могут быть изменены в будущих выпусках.These types should be considered unstable and subject to change in future releases.

Порядковые номера связаны с номерами строк кода, а не с порядком выполненияSequence numbers relate to code line numbers and not execution order

Файлы блазор .razor всегда компилируются.Blazor .razor files are always compiled. Это, вероятно, является отличным преимуществом для .razor, так как этап компиляции можно использовать для вставки сведений, повышающих производительность приложения во время выполнения.This is potentially a great advantage for .razor because the compile step can be used to inject information that improve app performance at runtime.

Ключевым примером этих улучшений являются порядковые номера.A key example of these improvements involve sequence numbers. Порядковые номера указывают среде выполнения, какие выходные данные поступили из разных и упорядоченных строк кода.Sequence numbers indicate to the runtime which outputs came from which distinct and ordered lines of code. Среда выполнения использует эти сведения для создания эффективных различий дерева в линейное время, что является гораздо быстрее, чем обычно возможно для общего алгоритма различения дерева.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.

Рассмотрим следующий простой файл .razor:Consider the following simple .razor file:

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

Second

Предыдущий код компилируется примерно следующим образом:The preceding code compiles to something like the following:

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

builder.AddContent(1, "Second");

Когда код выполняется впервые, если someFlag равно true, построитель получит следующее:When the code executes for the first time, if someFlag is true, the builder receives:

SequenceSequence TypeType DataData
00 Текстовый узелText node FirstFirst
11 Текстовый узелText node SecondSecond

Представьте, что someFlag становится false, а разметка снова готовится к просмотру.Imagine that someFlag becomes false, and the markup is rendered again. На этот раз построитель получит:This time, the builder receives:

SequenceSequence TypeType DataData
11 Текстовый узелText node SecondSecond

Когда среда выполнения выполняет различие, она видит, что элемент в последовательности 0 был удален, поэтому он создает следующий тривиальный сценарий редактирования:When the runtime performs a diff, it sees that the item at sequence 0 was removed, so it generates the following trivial edit script:

  • Удалите первый текстовый узел.Remove the first text node.

Что становится неправильным, если вы создаете порядковые номера программным способомWhat goes wrong if you generate sequence numbers programmatically

Вместо этого Представьте себе, что вы написали следующую логику конструктора деревьев визуализации:Imagine instead that you wrote the following render tree builder logic:

var seq = 0;

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

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

Теперь первые выходные данные:Now, the first output is:

SequenceSequence TypeType DataData
00 Текстовый узелText node FirstFirst
11 Текстовый узелText node SecondSecond

Этот результат идентичен предыдущему случаю, поэтому отрицательные проблемы не возникают.This outcome is identical to the prior case, so no negative issues exist. someFlag false при второй отрисовке, а выходные данные:someFlag is false on the second rendering, and the output is:

SequenceSequence TypeType DataData
00 Текстовый узелText node SecondSecond

На этот раз алгоритм diff видит, что были внесены два изменения, и алгоритм создает следующий сценарий редактирования:This time, the diff algorithm sees that two changes have occurred, and the algorithm generates the following edit script:

  • Измените значение первого текстового узла на Second.Change the value of the first text node to Second.
  • Удалите второй текстовый узел.Remove the second text node.

При формировании порядковых номеров теряются все полезные сведения о том, где в исходном коде представлены ветви и циклы if/else.Generating the sequence numbers has lost all the useful information about where the if/else branches and loops were present in the original code. Это приводит к удвоению в два раза больше , чем раньше.This results in a diff twice as long as before.

Это тривиальный пример.This is a trivial example. В более реалистичных случаях со сложными и глубокими вложенными структурами, особенно с циклами, затраты на производительность более серьезны.In more realistic cases with complex and deeply nested structures, and especially with loops, the performance cost is more severe. Вместо того, чтобы сразу определять, какие блоки или ветви цикла были вставлены или удалены, алгоритм diff должен рекурсивно пребираться в деревьях отрисовки и, как правило, создает гораздо более длительные операции редактирования, так как он сообщает о том, как старую и новую структуры связь друг с другом.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 and usually build far longer edit scripts because it is misinformed about how the old and new structures relate to each other.

Руководство и выводыGuidance and conclusions

  • Производительность приложения снижается, если порядковые номера создаются динамически.App performance suffers if sequence numbers are generated dynamically.
  • Платформа не может автоматически создавать собственные порядковые номера во время выполнения, поскольку необходимая информация не существует, если она не захвачена во время компиляции.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.
  • Не записывайте длинные блоки, реализованные вручную RenderTreeBuilder.Don't write long blocks of manually-implemented RenderTreeBuilder logic. Предпочитать файлы .razor и позволяют компилятору работать с порядковыми номерами.Prefer .razor files and allow the compiler to deal with the sequence numbers. Если не удается избежать ручного RenderTreeBuilder логики, разделите длинные блоки кода на более мелкие части, заключенные в OpenRegion / CloseRegion вызовы.If you're unable to avoid manual RenderTreeBuilder logic, split long blocks of code into smaller pieces wrapped in OpenRegion/CloseRegion calls. Каждый регион имеет собственное отдельное пространство порядковых номеров, поэтому вы можете перезапускаться от нуля (или любого другого произвольного числа) внутри каждого региона.Each region has its own separate space of sequence numbers, so you can restart from zero (or any other arbitrary number) inside each region.
  • Если порядковые номера задаются жестко, то для алгоритма diff требуется, чтобы только порядковые номера увеличиваются в значении.If sequence numbers are hardcoded, the diff algorithm only requires that sequence numbers increase in value. Начальное значение и зазоры несущественны.The initial value and gaps are irrelevant. Один из этих вариантов — использовать номер строки кода в качестве порядкового номера или начать с нуля и увеличить на единицу или сотни (или любой другой интервал).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 uses sequence numbers, while other tree-diffing UI frameworks don't use them. Сравнение выполняется гораздо быстрее, если используются порядковые номера, и Блазор имеет преимущество этапа компиляции, который автоматически обрабатывает порядковые номера для разработчиков, занимающихся созданием файлов .razor.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.

ЛокализацияLocalization

Блазор серверные приложения локализованы по промежуточного слоя локализации.Blazor Server apps are localized using Localization Middleware. По промежуточного слоя выбирает соответствующие языки и региональные параметры для пользователей, запрашивающих ресурсы из приложения.The middleware selects the appropriate culture for users requesting resources from the app.

Язык и региональные параметры можно задать с помощью одного из следующих подходов:The culture can be set using one of the following approaches:

Дополнительные сведения и примеры см. в разделе Глобализация и локализация в ASP.NET Core.For more information and examples, see Глобализация и локализация в ASP.NET Core.

Файлы cookieCookies

Файл cookie культуры локализации может сохранять язык и региональные параметры пользователя.A localization culture cookie can persist the user's culture. Файл cookie создается методом OnGet страницы узла приложения (pages/Host. cshtml. CS).The cookie is created by the OnGet method of the app's host page (Pages/Host.cshtml.cs). По промежуточного слоя локализации считывает файл cookie при последующих запросах, чтобы задать язык и региональные параметры пользователя.The Localization Middleware reads the cookie on subsequent requests to set the user's culture.

Использование файла cookie гарантирует, что соединение WebSocket сможет правильно распространить культуру.Use of a cookie ensures that the WebSocket connection can correctly propagate the culture. Если схемы локализации основаны на пути URL-адреса или строке запроса, схема может не поддерживать работу с WebSockets, поэтому она не сохраняет культуру.If localization schemes are based on the URL path or query string, the scheme might not be able to work with WebSockets, thus fail to persist the culture. Поэтому рекомендуемым подходом является использование файла cookie языка и региональных параметров локализации.Therefore, use of a localization culture cookie is the recommended approach.

Любой метод можно использовать для назначения языка и региональных параметров, если язык и региональные параметры сохраняются в файле cookie локализации.Any technique can be used to assign a culture if the culture is persisted in a localization cookie. Если у приложения уже есть установленная схема локализации для ASP.NET Core на стороне сервера, продолжайте использовать существующую инфраструктуру локализации приложения и задавайте файл cookie культуры локализации в схеме приложения.If the app already has an established localization scheme for server-side ASP.NET Core, continue to use the app's existing localization infrastructure and set the localization culture cookie within the app's scheme.

В следующем примере показано, как задать текущий язык и региональные параметры в файле cookie, который может быть прочитан по промежуточного слоя локализации.The following example shows how to set the current culture in a cookie that can be read by the Localization Middleware. Создайте файл pages/Host. cshtml. CS со следующим содержимым в приложении блазор Server:Create a Pages/Host.cshtml.cs file with the following contents in the Blazor Server app:

public class HostModel : PageModel
{
    public void OnGet()
    {
        HttpContext.Response.Cookies.Append(
            CookieRequestCultureProvider.DefaultCookieName,
            CookieRequestCultureProvider.MakeCookieValue(
                new RequestCulture(
                    CultureInfo.CurrentCulture,
                    CultureInfo.CurrentUICulture)));
    }
}

Локализация обрабатывается в приложении:Localization is handled in the app:

  1. Браузер отправляет в приложение исходный HTTP-запрос.The browser sends an initial HTTP request to the app.
  2. Язык и региональные параметры назначаются по промежуточного слоя локализации.The culture is assigned by the Localization Middleware.
  3. Метод OnGet в _Host. cshtml. CS сохраняет язык и региональные параметры в файле cookie как часть ответа.The OnGet method in _Host.cshtml.cs persists the culture in a cookie as part of the response.
  4. Браузер открывает соединение WebSocket для создания интерактивного сеанса Блазор Server.The browser opens a WebSocket connection to create an interactive Blazor Server session.
  5. По промежуточного слоя для локализации считывает файл cookie и назначает язык и региональные параметры.The Localization Middleware reads the cookie and assigns the culture.
  6. Сеанс сервера Блазор начинается с правильного языка и региональных параметров.The Blazor Server session begins with the correct culture.

Предоставление пользовательского интерфейса для выбора языка и региональных параметровProvide UI to choose the culture

Для предоставления пользовательского интерфейса, позволяющего пользователю выбирать язык и региональные параметры, рекомендуется подход на основе перенаправления .To provide UI to allow a user to select a culture, a redirect-based approach is recommended. Процесс аналогичен тому, что происходит в веб-приложении, когда пользователь пытается получить доступ к защищенному ресурсу, —the пользователь перенаправляется на страницу входа, а затем перенаправляется обратно к исходному ресурсу.The process is similar to what happens in a web app when a user attempts to access a secure resource—the user is redirected to a sign-in page and then redirected back to the original resource.

Приложение сохраняет выбранный язык и региональные параметры пользователя с помощью перенаправления к контроллеру.The app persists the user's selected culture via a redirect to a controller. Контроллер устанавливает выбранный язык и региональные параметры пользователя в файл cookie и перенаправляет пользователя обратно к исходному коду URI.The controller sets the user's selected culture into a cookie and redirects the user back to the original URI.

Установите конечную точку HTTP на сервере, чтобы задать выбранный язык и региональные параметры пользователя в файле cookie, и выполните перенаправление обратно в исходный URI:Establish an HTTP endpoint on the server to set the user's selected culture in a cookie and perform the redirect back to the original URI:

[Route("[controller]/[action]")]
public class CultureController : Controller
{
    public IActionResult SetCulture(string culture, string redirectUri)
    {
        if (culture != null)
        {
            HttpContext.Response.Cookies.Append(
                CookieRequestCultureProvider.DefaultCookieName,
                CookieRequestCultureProvider.MakeCookieValue(
                    new RequestCulture(culture)));
        }

        return LocalRedirect(redirectUri);
    }
}

Предупреждение

Используйте результат действия LocalRedirect, чтобы предотвратить атаки с открытым перенаправлением.Use the LocalRedirect action result to prevent open redirect attacks. Для получения дополнительной информации см. Предотвращение атак открытого перенаправления в ASP.NET Core.For more information, see Предотвращение атак открытого перенаправления в ASP.NET Core.

В следующем компоненте показан пример выполнения начального перенаправления, когда пользователь выбирает язык и региональные параметры:The following component shows an example of how to perform the initial redirection when the user selects a culture:

@inject NavigationManager NavigationManager

<h3>Select your language</h3>

<select @onchange="OnSelected">
    <option>Select...</option>
    <option value="en-US">English</option>
    <option value="fr-FR">Français</option>
</select>

@code {
    private double textNumber;

    private void OnSelected(ChangeEventArgs e)
    {
        var culture = (string)e.Value;
        var uri = new Uri(NavigationManager.Uri())
            .GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped);
        var query = $"?culture={Uri.EscapeDataString(culture)}&" +
            $"redirectUri={Uri.EscapeDataString(uri)}";

        NavigationManager.NavigateTo("/Culture/SetCulture" + query, forceLoad: true);
    }
}

Использование сценариев локализации .NET в приложениях БлазорUse .NET localization scenarios in Blazor apps

В приложениях Блазор доступны следующие сценарии локализации и глобализации .NET:Inside Blazor apps, the following .NET localization and globalization scenarios are available:

  • . Система ресурсов NET.NET's resources system
  • Форматирование чисел и дат, зависящих от языка и региональных параметровCulture-specific number and date formatting

Функция @bind блазор выполняет глобализацию на основе текущего языка и региональных параметров пользователя.Blazor's @bind functionality performs globalization based on the user's current culture. Дополнительные сведения см. в разделе Привязка данных .For more information, see the Data binding section.

В настоящее время поддерживается ограниченный набор сценариев локализации ASP.NET Core:A limited set of ASP.NET Core's localization scenarios are currently supported:

  • IStringLocalizer<> поддерживается в приложениях блазор.IStringLocalizer<> is supported in Blazor apps.
  • IHtmlLocalizer<>, IViewLocalizer<> и локализация заметок к данным — ASP.NET Core сценарии MVC и не поддерживаются в приложениях блазор.IHtmlLocalizer<>, IViewLocalizer<>, and Data Annotations localization are ASP.NET Core MVC scenarios and not supported in Blazor apps.

Для получения дополнительной информации см. Глобализация и локализация в ASP.NET Core.For more information, see Глобализация и локализация в ASP.NET Core.

Масштабируемые изображения векторной графики (SVG)Scalable Vector Graphics (SVG) images

Поскольку Блазор визуализирует HTML-файлы, поддерживаемые браузером изображения, включая масштабируемые векторные графики (SVG ), поддерживаются с помощью тега <img>:Since Blazor renders HTML, browser-supported images, including Scalable Vector Graphics (SVG) images (.svg), are supported via the <img> tag:

<img alt="Example image" src="some-image.svg" />

Аналогичным образом SVG-изображения поддерживаются в правилах CSS файла стилей ( . CSS):Similarly, SVG images are supported in the CSS rules of a stylesheet file (.css):

.my-element {
    background-image: url("some-image.svg");
}

Однако встроенная разметка SVG не поддерживается во всех сценариях.However, inline SVG markup isn't supported in all scenarios. Если поместить тег <svg> непосредственно в файл компонента (Razor), то поддерживается базовая визуализация изображений, но многие расширенные сценарии еще не поддерживаются.If you place an <svg> tag directly into a component file (.razor), basic image rendering is supported but many advanced scenarios aren't yet supported. Например, в настоящее время Теги <use> не учитываются, и @bind нельзя использовать с некоторыми тегами SVG.For example, <use> tags aren't currently respected, and @bind can't be used with some SVG tags. В будущем выпуске мы планируем устранить эти ограничения.We expect to address these limitations in a future release.

Дополнительные ресурсыAdditional resources