ASP.NET Core Blazor 연계 값 및 매개 변수

참고 항목

이 문서의 최신 버전은 아닙니다. 현재 릴리스는 이 문서의 .NET 8 버전을 참조 하세요.

Important

이 정보는 상업적으로 출시되기 전에 실질적으로 수정될 수 있는 시험판 제품과 관련이 있습니다. Microsoft는 여기에 제공된 정보에 대해 어떠한 명시적, 또는 묵시적인 보증을 하지 않습니다.

현재 릴리스는 이 문서의 .NET 8 버전을 참조 하세요.

이 문서는 상위 Razor 구성 요소에서 하위 구성 요소로 데이터를 전달하는 방법을 설명합니다.

‘연계 값 및 매개 변수’는 구성 요소 계층 구조의 상위 구성 요소에서 원하는 수의 하위 구성 요소로 데이터를 전달하는 편리한 방법을 제공합니다. 구성 요소 매개 변수와 달리 연계 값 및 매개 변수는 데이터가 사용되는 각 하위 구성 요소에 대해 특성을 할당할 필요가 없습니다. 또한 연계 값 및 매개 변수를 사용하여 구성 요소는 구성 요소 계층 구조에서 서로 조정할 수 있습니다.

참고 항목

이 문서의 코드 예제에서는 NRT(nullable 참조 형식) 및 .NET 컴파일러 null 상태 정적 분석을 채택합니다. 이 분석은 .NET 6 이상의 ASP.NET Core에서 지원됩니다. ASP.NET Core 5.0 이하를 대상으로 하는 경우 아티클 예제의 , , RenderFragment?@ActiveTab?TabSet?ITab?string? 형식에서 CascadingType?null 형식 지정(?)을 제거합니다.

루트 수준 연계 값

루트 수준 연계 값은 전체 구성 요소 계층 구조에 대해 등록할 수 있습니다. 업데이트 알림에 대한 명명된 연계 값 및 구독이 지원됩니다.

다음 클래스는 이 섹션의 예제에서 사용됩니다.

Dalek.cs:

// "Dalek" ©Terry Nation https://www.imdb.com/name/nm0622334/
// "Doctor Who" ©BBC https://www.bbc.co.uk/programmes/b006q2x0

namespace BlazorSample;

public class Dalek
{
    public int Units { get; set; }
}

앱 파일AddCascadingValue에서 Program 다음을 사용하여 등록합니다.

  • Dalek 속성 값 Units 이 고정된 연계 값으로 등록됩니다.
  • 다른 속성 값 Units 이 있는 두 번째 Dalek 등록의 이름은 "AlphaGroup"입니다.
builder.Services.AddCascadingValue(sp => new Dalek { Units = 123 });
builder.Services.AddCascadingValue("AlphaGroup", sp => new Dalek { Units = 456 });

다음 Daleks 구성 요소는 계단식 값을 표시합니다.

Daleks.razor:

@page "/daleks"

<PageTitle>Daleks</PageTitle>

<h1>Root-level Cascading Value Example</h1>

<ul>
    <li>Dalek Units: @Dalek?.Units</li>
    <li>Alpha Group Dalek Units: @AlphaGroupDalek?.Units</li>
</ul>

<p>
    Dalek© <a href="https://www.imdb.com/name/nm0622334/">Terry Nation</a><br>
    Doctor Who© <a href="https://www.bbc.co.uk/programmes/b006q2x0">BBC</a>
</p>

@code {
    [CascadingParameter]
    public Dalek? Dalek { get; set; }

    [CascadingParameter(Name = "AlphaGroup")]
    public Dalek? AlphaGroupDalek { get; set; }
}

다음 예제 Dalek 에서는 형식이 있는 경우 <T> 를 사용하여 CascadingValueSource<T>연계 값으로 등록됩니다. 플래그는 isFixed 값이 고정되었는지 여부를 나타냅니다. false이면 모든 받는 사람이 업데이트 알림을 구독합니다. 이 알림은 호출 NotifyChangedAsync하여 발급됩니다. 구독은 오버헤드를 만들고 성능을 줄이므로 값이 변경되지 않는 경우로 true 설정합니다isFixed.

builder.Services.AddCascadingValue(sp =>
{
    var dalek = new Dalek { Units = 789 };
    var source = new CascadingValueSource<Dalek>(dalek, isFixed: false);
    return source;
});

Warning

구성 요소 형식을 루트 수준 연계 값으로 등록해도 해당 형식에 대한 추가 서비스를 등록하거나 구성 요소에서 서비스 활성화를 허용하지 않습니다.

필수 서비스를 연계 값과 별도로 처리하여 계단식 형식과 별도로 등록합니다.

구성 요소 형식을 연계 값으로 등록하는 데 사용하지 AddCascadingValue 마십시오. 대신 구성 요소(Components/Routes.razor)에서 Routes 구성 요소를 래핑 <Router>...</Router> 하고 전역 대화형 서버 쪽 렌더링(대화형 SSR)을 채택합니다. 예를 들어 구성 요소 섹션을 CascadingValue 참조하세요.

CascadingValue 구성 요소

상위 구성 요소는 Blazor 프레임워크의 CascadingValue 구성 요소를 사용하여 연계 값을 제공합니다. 이 구성 요소는 구성 요소 계층 구조의 하위 트리를 래핑하고 하위 트리 내의 모든 구성 요소에 단일 값을 제공합니다.

다음 예제에서는 자식 구성 요소의 단추에 CSS 스타일 클래스를 제공하기 위해 구성 요소 계층 구조에서 테마 정보의 흐름을 보여 줍니다.

다음 ThemeInfo C# 클래스는 테마 정보를 지정합니다.

참고 항목

이 섹션의 예제에서 앱의 네임스페이스는 BlazorSample입니다. 사용자 고유의 샘플 앱에서 코드를 시험할 때 앱의 네임스페이스를 샘플 앱의 네임스페이스로 변경합니다.

ThemeInfo.cs:

namespace BlazorSample;

public class ThemeInfo
{
    public string? ButtonClass { get; set; }
}
namespace BlazorSample.UIThemeClasses;

public class ThemeInfo
{
    public string? ButtonClass { get; set; }
}
namespace BlazorSample.UIThemeClasses;

public class ThemeInfo
{
    public string? ButtonClass { get; set; }
}
namespace BlazorSample.UIThemeClasses
{
    public class ThemeInfo
    {
        public string ButtonClass { get; set; }
    }
}
namespace BlazorSample.UIThemeClasses
{
    public class ThemeInfo
    {
        public string ButtonClass { get; set; }
    }
}

다음 레이아웃 구성 요소Body 속성의 레이아웃 본문을 구성하는 모든 구성 요소에 대한 연계 값으로 테마 정보(ThemeInfo)를 지정합니다. ButtonClass에는 부트스트랩 단추 스타일인 btn-success 값이 할당됩니다. 구성 요소 계층 구조의 모든 하위 구성 요소는 ThemeInfo 연계 값을 통해 ButtonClass 속성을 사용할 수 있습니다.

MainLayout.razor:

@inherits LayoutComponentBase

<div class="page">
    <div class="sidebar">
        <NavMenu />
    </div>

    <main>
        <div class="top-row px-4">
            <a href="https://learn.microsoft.com/aspnet/core/" target="_blank">About</a>
        </div>

        <CascadingValue Value="@theme">
            <article class="content px-4">
                @Body
            </article>
        </CascadingValue>
    </main>
</div>

<div id="blazor-error-ui" data-nosnippet>
    An unhandled error has occurred.
    <a href="" class="reload">Reload</a>
    <a class="dismiss">🗙</a>
</div>

@code {
    private ThemeInfo theme = new() { ButtonClass = "btn-success" };
}
@inherits LayoutComponentBase
@using BlazorSample.UIThemeClasses

<div class="page">
    <div class="sidebar">
        <NavMenu />
    </div>

    <main>
        <div class="top-row px-4">
            <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
        </div>

        <CascadingValue Value="@theme">
            <article class="content px-4">
                @Body
            </article>
        </CascadingValue>
    </main>
</div>

@code {
    private ThemeInfo theme = new() { ButtonClass = "btn-success" };
}
@inherits LayoutComponentBase
@using BlazorSample.UIThemeClasses

<div class="page">
    <div class="sidebar">
        <NavMenu />
    </div>

    <main>
        <CascadingValue Value="@theme">
            <div class="content px-4">
                @Body
            </div>
        </CascadingValue>
    </main>
</div>

@code {
    private ThemeInfo theme = new() { ButtonClass = "btn-success" };
}
@inherits LayoutComponentBase
@using BlazorSample.UIThemeClasses

<div class="page">
    <div class="sidebar">
        <NavMenu />
    </div>

    <div class="main">
        <CascadingValue Value="@theme">
            <div class="content px-4">
                @Body
            </div>
        </CascadingValue>
    </div>
</div>

@code {
    private ThemeInfo theme = new() { ButtonClass = "btn-success" };
}
@inherits LayoutComponentBase
@using BlazorSample.UIThemeClasses

<div class="sidebar">
    <NavMenu />
</div>

<div class="main">
    <CascadingValue Value="theme">
        <div class="content px-4">
            @Body
        </div>
    </CascadingValue>
</div>

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

Blazor Web Apps는 단일 레이아웃 파일을 통해 제공하는 것보다 앱에 더 광범위하게 적용되는 연계 값에 대한 대체 방법을 제공합니다.

  • 구성 요소의 태그를 RoutesCascadingValue 구성 요소에 래핑하여 데이터를 모든 앱의 구성 요소에 대한 연계 값으로 지정합니다.

    다음 예제에서는 구성 요소의 데이터를 연계 ThemeInfo 합니다 Routes .

    Routes.razor:

    <CascadingValue Value="theme">
        <Router ...>
            ...
        </Router>
    </CascadingValue>
    
    @code {
        private ThemeInfo theme = new() { ButtonClass = "btn-success" };
    }
    

    참고 항목

    구성 요소를 사용하여 Routes 구성 요소(Components/App.razor)의 App 구성 요소 인스턴스 래 CascadingValue 핑은 지원되지 않습니다.

  • 서비스 컬렉션 작성기에서 확장 메서드를 호출 AddCascadingValue 하여 루트 수준 연계 값을 서비스로 지정합니다.

    다음 예제에서는 파일의 데이터를 연계 ThemeInfo 합니다 Program .

    Program.cs

    builder.Services.AddCascadingValue(sp => 
        new ThemeInfo() { ButtonClass = "btn-primary" });
    

자세한 내용은 이 문서의 다음 섹션을 참조하세요.

[CascadingParameter] 특성

연계 값을 사용하기 위해 하위 구성 요소는 [CascadingParameter] 특성을 사용하여 연계 매개 변수를 선언합니다. 연계 값은 형식별로 연계 매개 변수에 바인딩됩니다. 같은 형식의 여러 값 연계는 이 문서의 뒷부분에 나오는 여러 값 연계 섹션에서 설명합니다.

다음 구성 요소는 ThemeInfo 연계 값을 연계 매개 변수에 바인딩하며, 필요에 따라 ThemeInfo의 동일한 이름을 사용합니다. 매개 변수는 Increment Counter (Themed) 단추에 대해 CSS 클래스를 설정하는 데 사용됩니다.

ThemedCounter.razor:

@page "/themed-counter"

<PageTitle>Themed Counter</PageTitle>

<h1>Themed Counter Example</h1>

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

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

<p>
    <button 
        class="btn @(ThemeInfo is not null ? ThemeInfo.ButtonClass : string.Empty)" 
        @onclick="IncrementCount">
        Increment Counter (Themed)
    </button>
</p>

@code {
    private int currentCount = 0;

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

    private void IncrementCount()
    {
        currentCount++;
    }
}
@page "/themed-counter"
@using BlazorSample.UIThemeClasses

<h1>Themed Counter</h1>

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

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

<p>
    <button 
        class="btn @(ThemeInfo is not null ? ThemeInfo.ButtonClass : string.Empty)" 
        @onclick="IncrementCount">
        Increment Counter (Themed)
    </button>
</p>

@code {
    private int currentCount = 0;

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

    private void IncrementCount()
    {
        currentCount++;
    }
}
@page "/themed-counter"
@using BlazorSample.UIThemeClasses

<h1>Themed Counter</h1>

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

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

<p>
    <button 
        class="btn @(ThemeInfo is not null ? ThemeInfo.ButtonClass : string.Empty)" 
        @onclick="IncrementCount">
        Increment Counter (Themed)
    </button>
</p>

@code {
    private int currentCount = 0;

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

    private void IncrementCount()
    {
        currentCount++;
    }
}
@page "/themed-counter"
@using BlazorSample.UIThemeClasses

<h1>Themed Counter</h1>

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

<p>
    <button @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++;
    }
}
@page "/themed-counter"
@using BlazorSample.UIThemeClasses

<h1>Themed Counter</h1>

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

<p>
    <button @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++;
    }
}

일반 구성 요소 매개 변수와 마찬가지로 계단식 배열 매개 변수를 허용하는 구성 요소는 계단식 배열 값이 변경될 때 다시 렌더링됩니다. 예를 들어 다른 테마 인스턴스를 구성하면 구성 요소 섹션의 ThemedCounterCascadingValue 구성 요소가 다시 렌더링됩니다.

MainLayout.razor:

<main>
    <div class="top-row px-4">
        <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
    </div>

    <CascadingValue Value="theme">
        <article class="content px-4">
            @Body
        </article>
    </CascadingValue>
    <button @onclick="ChangeToDarkTheme">Dark mode</button>
</main>

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

    private void ChangeToDarkTheme()
    {
        theme = new() { ButtonClass = "btn-secondary" };
    }
}

CascadingValue<TValue>.IsFixed는 초기화 후 계단식 배열 매개 변수가 변경되지 않음을 나타내는 데 사용할 수 있습니다.

연계 값/매개 변수 및 렌더링 모드 경계

연계 매개 변수는 렌더링 모드 경계를 넘어 데이터를 전달하지 않습니다.

  • 대화형 세션은 정적 서버 쪽 렌더링(정적 SSR)을 사용하는 페이지와 다른 컨텍스트에서 실행됩니다. 페이지를 생성하는 서버가 클라이언트와 다른 컴퓨터인 WebAssembly 구성 요소를 포함하여 나중에 대화형 서버 세션을 호스트하는 컴퓨터와 동일한 컴퓨터일 필요는 없습니다. 정적 서버 쪽 렌더링(정적 SSR)의 이점은 순수 상태 비저장 HTML 렌더링의 전체 성능을 얻는 것입니다.

  • 정적 렌더링과 대화형 렌더링 사이의 경계를 넘은 상태는 직렬화할 수 있어야 합니다. 구성 요소는 렌더러, DI 컨테이너 및 모든 DI 서비스 인스턴스를 비롯한 방대한 다른 개체 체인을 참조하는 임의의 개체입니다. 정적 SSR에서 상태를 직렬화하여 후속 대화형으로 렌더링된 구성 요소에서 사용할 수 있도록 명시적으로 해야 합니다. 다음 두 가지 방법을 채택합니다.

    • 프레임워크를 Blazor 통해 정적 SSR을 통해 대화형 렌더링 경계로 전달되는 매개 변수는 ON 직렬화가 가능하거나 오류가 throw되는 경우 JS자동으로 직렬화됩니다.
    • ON 직렬화가 가능하거나 오류가 throw되면 저장된 PersistentComponentState 상태가 직렬화되고 자동으로 JS복구됩니다.

연계 매개 변수의 일반적인 사용 패턴은 DI 서비스와 유사하므로 연계 매개 변수는 ON 직렬화할 수 없습니다 JS. 종종 플랫폼별 종속 매개 변수 변형이 있으므로 프레임워크에서 개발자가 서버 대화형 특정 버전 또는 WebAssembly 관련 버전을 사용하지 못하게 하는 경우 개발자에게 도움이 되지 않습니다. 또한 일반적으로 많은 연계 매개 변수 값은 직렬화할 수 없으므로 모든 역직렬화할 수 없는 연계 매개 변수 값 사용을 중지해야 하는 경우 기존 앱을 업데이트하는 것은 실용적이지 않습니다.

권장 사항:

  • 모든 대화형 구성 요소에서 연계 매개 변수로 상태를 사용할 수 있도록 해야 하는 경우 루트 수준 연계 값을 사용하는 것이 좋습니다. 팩터리 패턴을 사용할 수 있으며 앱 시작 후 앱에서 업데이트된 값을 내보낼 수 있습니다. 루트 수준 연계 값은 DI 서비스로 처리되므로 대화형 구성 요소를 비롯한 모든 구성 요소에서 사용할 수 있습니다.

  • 구성 요소 라이브러리 작성자의 경우 다음과 유사한 라이브러리 소비자를 위한 확장 메서드를 만들 수 있습니다.

    builder.Services.AddLibraryCascadingParameters();
    

    개발자에게 확장 메서드를 호출하도록 지시합니다. 이는 구성 요소에 구성 요소를 추가 <RootComponent> 하도록 지시하는 소리 대신 사용할 수 MainLayout 있습니다.

여러 값 연계

같은 하위 트리 내에서 같은 형식의 여러 값을 연계하려면 각 CascadingValue 구성 요소 및 해당 [CascadingParameter] 특성에 고유한 Name 문자열을 제공합니다.

다음 예제에서는 두 개의 CascadingValue 구성 요소가 CascadingType의 다른 인스턴스를 연계합니다.

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

@code {
    private CascadingType? parentCascadeParameter1;

    [Parameter]
    public CascadingType? ParentCascadeParameter2 { get; set; }
}

하위 구성 요소에서 연계된 매개 변수는 상위 구성 요소에서 연계된 값을 Name별로 받습니다.

@code {
    [CascadingParameter(Name = "CascadeParam1")]
    protected CascadingType? ChildCascadeParameter1 { get; set; }

    [CascadingParameter(Name = "CascadeParam2")]
    protected CascadingType? ChildCascadeParameter2 { get; set; }
}

구성 요소 계층 구조에서 데이터 전달

연계 매개 변수를 사용하면 구성 요소가 구성 요소 계층 구조에서 데이터를 전달할 수도 있습니다. 다음 UI 탭 집합 예제를 살펴보면 탭 집합 구성 요소가 일련의 개별 탭을 유지 관리합니다.

참고 항목

이 섹션의 예제에서 앱의 네임스페이스는 BlazorSample입니다. 사용자 고유의 샘플 앱에서 코드를 시험할 때 네임스페이스를 샘플 앱의 네임스페이스로 변경합니다.

UIInterfaces라는 폴더에 탭이 구현하는 ITab 인터페이스를 만듭니다.

UIInterfaces/ITab.cs:

using Microsoft.AspNetCore.Components;

namespace BlazorSample.UIInterfaces;

public interface ITab
{
    RenderFragment ChildContent { get; }
}

참고 항목

RenderFragment에 대한 자세한 내용은 ASP.NET Core Razor 구성 요소를 참조하세요.

다음 TabSet 구성 요소는 탭 집합을 유지 관리합니다. 이 섹션의 뒷부분에서 만드는 탭 집합의 Tab 구성 요소는 목록(<ul>...</ul>)에 대해 목록 항목(<li>...</li>)을 제공합니다.

자식 Tab 구성 요소는 매개 변수로 TabSet에 명시적으로 전달되지 않습니다. 대신, 자식 Tab 구성 요소는 TabSet의 자식 콘텐츠에 속합니다. 그러나 TabSet 헤더와 활성 탭을 렌더링할 수 있도록 여전히 각 Tab 구성 요소에 대한 참조가 필요합니다. 추가 코드를 TabSet 요구하지 않고 이 조정을 사용하도록 설정하기 위해 구성 요소는 하위 Tab 구성 요소에 의해 선택되는 연계 값으로 자신을 제공할 수 있습니다.

TabSet.razor:

@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 is null)
        {
            SetActiveTab(tab);
        }
    }

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

하위 Tab 구성 요소는 포함하는 TabSet를 연계 매개 변수로 캡처합니다. Tab 구성 요소는 TabSet에 자신을 추가하고 조정하여 활성 탭을 설정합니다.

Tab.razor:

@using BlazorSample.UIInterfaces
@implements ITab

<li>
    <a @onclick="ActivateTab" 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 ActivateTab()
    {
        ContainerTabSet?.SetActiveTab(this);
    }
}

다음 ExampleTabSet 구성 요소는 세 개의 Tab 구성 요소를 포함하는 TabSet 구성 요소를 사용합니다.

ExampleTabSet.razor:

@page "/example-tab-set"

<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>Hello from the second tab!</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>

@code {
    private bool showThirdTab;
}

추가 리소스