다음을 통해 공유


ASP.NET Core Blazor 데이터 바인딩

참고 항목

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

Warning

이 버전의 ASP.NET Core는 더 이상 지원되지 않습니다. 자세한 내용은 .NET 및 .NET Core 지원 정책을 참조 하세요. 현재 릴리스는 이 문서의 .NET 9 버전을 참조 하세요.

Important

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

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

이 문서에서는 앱의 구성 요소 및 DOM 요소에 Blazor 대한 Razor 데이터 바인딩 기능을 설명합니다.

바인딩 기능

Razor구성 요소는 필드, 속성 또는 Razor 식 값이 @bindRazor 있는 지시문 특성과 함께 데이터 바인딩 기능을 제공합니다.

다음 예제에서는 다음과 같이 바인딩합니다.

  • <input> 요소 값을 C# inputValue 필드에 바인딩합니다.
  • 두 번째 <input> 요소 값을 C# InputValue 속성에 바인딩합니다.

<input> 요소가 포커스를 잃으면 바인딩된 필드 또는 속성이 업데이트됩니다.

Bind.razor:

@page "/bind"

<PageTitle>Bind</PageTitle>

<h1>Bind Example</h1>

<p>
    <label>
        inputValue: 
        <input @bind="inputValue" />
    </label>
</p>

<p>
    <label>
        InputValue: 
        <input @bind="InputValue" />
    </label>
</p>

<ul>
    <li><code>inputValue</code>: @inputValue</li>
    <li><code>InputValue</code>: @InputValue</li>
</ul>

@code {
    private string? inputValue;

    private string? InputValue { get; set; }
}
@page "/bind"

<PageTitle>Bind</PageTitle>

<h1>Bind Example</h1>

<p>
    <label>
        inputValue: 
        <input @bind="inputValue" />
    </label>
</p>

<p>
    <label>
        InputValue: 
        <input @bind="InputValue" />
    </label>
</p>

<ul>
    <li><code>inputValue</code>: @inputValue</li>
    <li><code>InputValue</code>: @InputValue</li>
</ul>

@code {
    private string? inputValue;

    private string? InputValue { get; set; }
}
@page "/bind"

<p>
    <input @bind="inputValue" />
</p>

<p>
    <input @bind="InputValue" />
</p>

<ul>
    <li><code>inputValue</code>: @inputValue</li>
    <li><code>InputValue</code>: @InputValue</li>
</ul>

@code {
    private string? inputValue;

    private string? InputValue { get; set; }
}
@page "/bind"

<p>
    <input @bind="inputValue" />
</p>

<p>
    <input @bind="InputValue" />
</p>

<ul>
    <li><code>inputValue</code>: @inputValue</li>
    <li><code>InputValue</code>: @InputValue</li>
</ul>

@code {
    private string? inputValue;

    private string? InputValue { get; set; }
}
@page "/bind"

<p>
    <input @bind="inputValue" />
</p>

<p>
    <input @bind="InputValue" />
</p>

<ul>
    <li><code>inputValue</code>: @inputValue</li>
    <li><code>InputValue</code>: @InputValue</li>
</ul>

@code {
    private string inputValue;

    private string InputValue { get; set; }
}
@page "/bind"

<p>
    <input @bind="inputValue" />
</p>

<p>
    <input @bind="InputValue" />
</p>

<ul>
    <li><code>inputValue</code>: @inputValue</li>
    <li><code>InputValue</code>: @InputValue</li>
</ul>

@code {
    private string inputValue;

    private string InputValue { get; set; }
}

텍스트 상자는 필드 또는 속성 값 변경에 대한 대응이 아니라, 구성 요소가 렌더링되는 경우에만 UI에서 업데이트됩니다. 이벤트 처리기 코드를 실행하면 구성 요소가 자체적으로 렌더링되므로 필드 및 속성 업데이트는 ‘일반적으로’ 이벤트 처리기가 트리거되는 즉시 UI에 반영됩니다.

데이터 바인딩이 HTML로 작성되는 방식을 보여 주기 위해 다음 예제에서는 InputValue 속성을 두 번째 <input> 요소의 valueonchange 특성(change)에 바인딩합니다. ‘다음 예제에서 두 번째 <input> 요소는 개념 데모이며 Razor 구성 요소에서 데이터를 바인딩하는 방법을 제안하지 않습니다.’

BindTheory.razor:

@page "/bind-theory"

<PageTitle>Bind Theory</PageTitle>

<h1>Bind Theory Example</h1>

<p>
    <label>
        Normal Blazor binding: 
        <input @bind="InputValue" />
    </label>
</p>

<p>
    <label>
        Demonstration of equivalent HTML binding: 
        <input value="@InputValue" @onchange="@((ChangeEventArgs __e) =>
            InputValue = __e?.Value?.ToString())" />
    </label>
</p>

<p>
    <code>InputValue</code>: @InputValue
</p>

@code {
    private string? InputValue { get; set; }
}
@page "/bind-theory"

<PageTitle>Bind Theory</PageTitle>

<h1>Bind Theory Example</h1>

<p>
    <label>
        Normal Blazor binding: 
        <input @bind="InputValue" />
    </label>
</p>

<p>
    <label>
        Demonstration of equivalent HTML binding: 
        <input value="@InputValue" @onchange="@((ChangeEventArgs __e) =>
            InputValue = __e?.Value?.ToString())" />
    </label>
</p>

<p>
    <code>InputValue</code>: @InputValue
</p>

@code {
    private string? InputValue { get; set; }
}
@page "/bind-theory"

<p>
    <label>
        Normal Blazor binding: 
        <input @bind="InputValue" />
    </label>
</p>

<p>
    <label>
        Demonstration of equivalent HTML binding: 
        <input value="@InputValue"
            @onchange="@((ChangeEventArgs __e) => InputValue = __e?.Value?.ToString())" />
    </label>
</p>

<p>
    <code>InputValue</code>: @InputValue
</p>

@code {
    private string? InputValue { get; set; }
}
@page "/bind-theory"

<p>
    <label>
        Normal Blazor binding: 
        <input @bind="InputValue" />
    </label>
</p>

<p>
    <label>
        Demonstration of equivalent HTML binding: 
        <input value="@InputValue"
            @onchange="@((ChangeEventArgs __e) => InputValue = __e?.Value?.ToString())" />
    </label>
</p>

<p>
    <code>InputValue</code>: @InputValue
</p>

@code {
    private string? InputValue { get; set; }
}
@page "/bind-theory"

<p>
    <label>
        Normal Blazor binding: 
        <input @bind="InputValue" />
    </label>
</p>

<p>
    <label>
        Demonstration of equivalent HTML binding: 
        <input value="@InputValue"
            @onchange="@((ChangeEventArgs __e) => InputValue = __e.Value.ToString())" />
    </label>
</p>

<p>
    <code>InputValue</code>: @InputValue
</p>

@code {
    private string InputValue { get; set; }
}
@page "/bind-theory"

<p>
    <label>
        Normal Blazor binding: 
        <input @bind="InputValue" />
    </label>
</p>

<p>
    <label>
        Demonstration of equivalent HTML binding: 
        <input value="@InputValue"
            @onchange="@((ChangeEventArgs __e) => InputValue = __e.Value.ToString())" />
    </label>
</p>

<p>
    <code>InputValue</code>: @InputValue
</p>

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

BindTheory 구성 요소가 렌더링되면 HTML 데모 <input> 요소의 valueInputValue 속성에서 가져옵니다. 사용자가 텍스트 상자에 값을 입력하고 요소 포커스를 변경하면 onchange 이벤트가 발생하고 InputValue 속성이 변경된 값으로 설정됩니다. 실제로는 @bind에서 형식 변환이 수행되는 경우를 처리하기 때문에 코드 실행이 더욱 복잡합니다. 일반적으로 @bind 식의 현재 값을 특성과 value <input> 연결하고 등록된 처리기를 사용하여 변경 내용을 처리합니다.

자리 표시자에 대한 DOM 이벤트가 있는 @bind:event="{EVENT}" 특성을 포함하여 다른 DOM 이벤트의 {EVENT} 속성 또는 필드를 바인딩합니다. 다음 예제에서는 <input> 요소의 oninput 이벤트(input)가 트리거될 때 해당 요소의 값에 InputValue 속성을 바인딩합니다. 요소가 포커스를 잃으면 발생하는 onchange 이벤트(change)와 달리 oninput(input)은 텍스트 상자의 값이 변경될 때 발생합니다.

Page/BindEvent.razor:

@page "/bind-event"

<PageTitle>Bind Event</PageTitle>

<h1>Bind Event Example</h1>

<p>
    <label>
        InputValue: 
        <input @bind="InputValue" @bind:event="oninput" />
    </label>
    
</p>

<p>
    <code>InputValue</code>: @InputValue
</p>

@code {
    private string? InputValue { get; set; }
}
@page "/bind-event"

<PageTitle>Bind Event</PageTitle>

<h1>Bind Event Example</h1>

<p>
    <label>
        InputValue: 
        <input @bind="InputValue" @bind:event="oninput" />
    </label>
    
</p>

<p>
    <code>InputValue</code>: @InputValue
</p>

@code {
    private string? InputValue { get; set; }
}
@page "/bind-event"

<p>
    <input @bind="InputValue" @bind:event="oninput" />
</p>

<p>
    <code>InputValue</code>: @InputValue
</p>

@code {
    private string? InputValue { get; set; }
}
@page "/bind-event"

<p>
    <input @bind="InputValue" @bind:event="oninput" />
</p>

<p>
    <code>InputValue</code>: @InputValue
</p>

@code {
    private string? InputValue { get; set; }
}
@page "/bind-event"

<p>
    <input @bind="InputValue" @bind:event="oninput" />
</p>

<p>
    <code>InputValue</code>: @InputValue
</p>

@code {
    private string InputValue { get; set; }
}
@page "/bind-event"

<p>
    <input @bind="InputValue" @bind:event="oninput" />
</p>

<p>
    <code>InputValue</code>: @InputValue
</p>

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

Razor 특성 바인딩은 대/소문자를 구분합니다.

  • @bind@bind:event는 유효합니다.
  • @Bind/@Bind:Event(대문자 BE) 또는 @BIND/@BIND:EVENT(모두 대문자) 는 잘못되었습니다.

바인딩 후 비동기 논리를 실행하려면 자리 표시자가 C# 대리자(메서드)인 위치를 {DELEGATE} 사용합니다@bind:after="{DELEGATE}". 할당된 C# 대리자는 바인딩된 값이 동기적으로 할당될 때까지 실행되지 않습니다.

이벤트 콜백 매개 변수(EventCallbackEventCallback<T>/)를 사용하는 @bind:after 것은 지원되지 않습니다. 대신, Action 또는 Task@bind:after에 반환하는 메서드를 전달합니다.

다음 예제에서

  • <input> 요소의 value 필드에 동기적으로 바인딩 searchText 됩니다.
  • 메서드는 PerformSearch 비동기적으로 실행됩니다.
    • 값이 변경된 후 첫 번째 상자에서 포커스(onchange 이벤트)가 손실되는 경우
    • 두 번째 상자의 각 키 입력(oninput 이벤트) 후
  • PerformSearch는 비동기 메서드(FetchAsync)를 사용하여 서비스를 호출함으로써 검색 결과를 반환합니다.
@inject ISearchService SearchService

<input @bind="searchText" @bind:after="PerformSearch" />
<input @bind="searchText" @bind:event="oninput" @bind:after="PerformSearch" />

@code {
    private string? searchText;
    private string[]? searchResult;

    private async Task PerformSearch() => 
        searchResult = await SearchService.FetchAsync(searchText);
}

추가 예시

BindAfter.razor:

@page "/bind-after"
@using Microsoft.AspNetCore.Components.Forms

<h1>Bind After Examples</h1>

<h2>Elements</h2>

<input type="text" @bind="text" @bind:after="() => { }" />

<input type="text" @bind="text" @bind:after="After" />

<input type="text" @bind="text" @bind:after="AfterAsync" />

<h2>Components</h2>

<InputText @bind-Value="text" @bind-Value:after="() => { }" />

<InputText @bind-Value="text" @bind-Value:after="After" />

<InputText @bind-Value="text" @bind-Value:after="AfterAsync" />

@code {
    private string text = "";

    private void After() {}
    private Task AfterAsync() { return Task.CompletedTask; }
}

구성 요소에 대한 InputText 자세한 내용은 ASP.NET Core Blazor 입력 구성 요소를 참조하세요.

구성 요소는 매개 변수 쌍을 정의하여 양방향 데이터 바인딩을 지원합니다.

  • @bind:get: 바인딩할 값을 지정합니다.
  • @bind:set: 값이 변경되는 경우에 대한 콜백을 지정합니다.

@bind:get@bind:set 한정자는 항상 함께 사용됩니다.

예제

BindGetSet.razor:

@page "/bind-get-set"
@using Microsoft.AspNetCore.Components.Forms

<h1>Bind Get Set Examples</h1>

<h2>Elements</h2>

<input type="text" @bind:get="text" @bind:set="(value) => { text = value; }" />
<input type="text" @bind:get="text" @bind:set="Set" />
<input type="text" @bind:get="text" @bind:set="SetAsync" />

<h2>Components</h2>

<InputText @bind-Value:get="text" @bind-Value:set="(value) => { text = value; }" />
<InputText @bind-Value:get="text" @bind-Value:set="Set" />
<InputText @bind-Value:get="text" @bind-Value:set="SetAsync" />

@code {
    private string text = "";

    private void Set(string value)
    {
        text = value;
    }

    private Task SetAsync(string value)
    {
        text = value;
        return Task.CompletedTask;
    }
}

구성 요소에 대한 InputText 자세한 내용은 ASP.NET Core Blazor 입력 구성 요소를 참조하세요.

사용 및 사용의 @bind:get 또 다른 예는 이 문서의 뒷부분에 있는 두 개 이상의 구성 요소 섹션에서 바인딩을 참조@bind:set하세요.

Razor 특성 바인딩은 대/소문자를 구분합니다.

  • @bind, @bind:event@bind:after는 유효합니다.
  • @Bind/@bind:Event/@bind:aftEr(대문자) 또는 @BIND/@BIND:EVENT/@BIND:AFTER(모두 대문자)는 유효하지 않습니다.

양방향 데이터 바인딩에 @bind:get/@bind:set 한정자를 사용하고 이벤트 처리기를 사용하지 않습니다

양방향 데이터 바인딩은 이벤트 처리기를 사용하여 구현할 수 없습니다. 양방향 데이터 바인딩에 @bind:get/@bind:set 한정자를 사용합니다.

이벤트 처리기를 사용하는 양방향 데이터 바인딩에 대해 다음과 같은 기능 장애 접근 방식을 고려합니다.

<p>
    <input value="@inputValue" @oninput="OnInput" />
</p>

<p>
    <code>inputValue</code>: @inputValue
</p>

@code {
    private string? inputValue;

    private void OnInput(ChangeEventArgs args)
    {
        var newValue = args.Value?.ToString() ?? string.Empty;

        inputValue = newValue.Length > 4 ? "Long!" : newValue;
    }
}

OnInput 이벤트 처리기는 네 번째 문자가 제공된 후 값을 inputValue에서 Long!으로 업데이트합니다. 그러나, 사용자는 UI의 요소 값에 문자를 계속 추가할 수 있습니다. inputValue의 값은 각 키 입력을 사용하여 요소의 값으로 다시 바인딩되지 않습니다. 앞의 예제는 단방향 데이터 바인딩만 수행할 수 있습니다.

이 동작의 이유는 코드가 이벤트 처리기에서 inputValue의 값을 수정하려고 한다는 것을 Blazor이 인식하지 못하기 때문입니다. Blazor 는 구문으로 바인딩 @bind 되지 않는 한 DOM 요소 값과 .NET 변수 값을 강제로 일치시키려고 하지 않습니다. 이전 버전의 Blazor에서, 양방향 데이터 바인딩은 요소를 속성에 바인딩하고 설정기를 사용하여 속성의 값을 제어하여 구현됩니다. .NET 7 이상의 @bind:get/@bind:set ASP.NET Core에서 한정자 구문은 다음 예제와 같이 양방향 데이터 바인딩을 구현하는 데 사용됩니다.

양방향 데이터 바인딩에 @bind:get/@bind:set를 사용하는 다음과 같은 올바른 접근방식을 고려합니다.

<p>
    <input @bind:event="oninput" @bind:get="inputValue" @bind:set="OnInput" />
</p>

<p>
    <code>inputValue</code>: @inputValue
</p>

@code {
    private string? inputValue;

    private void OnInput(string value)
    {
        var newValue = value ?? string.Empty;

        inputValue = newValue.Length > 4 ? "Long!" : newValue;
    }
}

@bind:get/@bind:set 한정자를 사용하면 둘 다 @bind:set를 통해 inputValue의 기본 값을 제어하고 @bind:get을 통해 inputValue의 값을 요소의 값에 바인딩합니다. 앞의 예제에서는 양방향 데이터 바인딩을 구현하기 위한 올바른 방법을 보여줍니다.

C# getset 접근자를 사용하여 속성에 바인딩

다음 DecimalBinding 구성 요소가 보여 주는 대로 C# getset 접근자를 사용하여 사용자 지정 바인딩 형식 동작을 만들 수 있습니다. 구성 요소는 string 속성(DecimalValue)을 통해 소수 자릿수가 최대 3자리인 양의 소수 또는 음의 소수를 <input> 요소에 바인딩합니다.

DecimalBinding.razor:

@page "/decimal-binding"
@using System.Globalization

<PageTitle>Decimal Binding</PageTitle>

<h1>Decimal Binding Example</h1>

<p>
    <label>
        Decimal value (±0.000 format):
        <input @bind="DecimalValue" />
    </label>
</p>

<p>
    <code>decimalValue</code>: @decimalValue
</p>

@code {
    private decimal decimalValue = 1.1M;
    private NumberStyles style = 
        NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign;
    private CultureInfo culture = CultureInfo.CreateSpecificCulture("en-US");

    private string DecimalValue
    {
        get => decimalValue.ToString("0.000", culture);
        set
        {
            if (Decimal.TryParse(value, style, culture, out var number))
            {
                decimalValue = Math.Round(number, 3);
            }
        }
    }
}
@page "/decimal-binding"
@using System.Globalization

<PageTitle>Decimal Binding</PageTitle>

<h1>Decimal Binding Example</h1>

<p>
    <label>
        Decimal value (±0.000 format):
        <input @bind="DecimalValue" />
    </label>
</p>

<p>
    <code>decimalValue</code>: @decimalValue
</p>

@code {
    private decimal decimalValue = 1.1M;
    private NumberStyles style = 
        NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign;
    private CultureInfo culture = CultureInfo.CreateSpecificCulture("en-US");

    private string DecimalValue
    {
        get => decimalValue.ToString("0.000", culture);
        set
        {
            if (Decimal.TryParse(value, style, culture, out var number))
            {
                decimalValue = Math.Round(number, 3);
            }
        }
    }
}
@page "/decimal-binding"
@using System.Globalization

<p>
    <label>
        Decimal value (±0.000 format):
        <input @bind="DecimalValue" />
    </label>
</p>

<p>
    <code>decimalValue</code>: @decimalValue
</p>

@code {
    private decimal decimalValue = 1.1M;
    private NumberStyles style = 
        NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign;
    private CultureInfo culture = CultureInfo.CreateSpecificCulture("en-US");

    private string DecimalValue
    {
        get => decimalValue.ToString("0.000", culture);
        set
        {
            if (Decimal.TryParse(value, style, culture, out var number))
            {
                decimalValue = Math.Round(number, 3);
            }
        }
    }
}
@page "/decimal-binding"
@using System.Globalization

<p>
    <label>
        Decimal value (±0.000 format):
        <input @bind="DecimalValue" />
    </label>
</p>

<p>
    <code>decimalValue</code>: @decimalValue
</p>

@code {
    private decimal decimalValue = 1.1M;
    private NumberStyles style = 
        NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign;
    private CultureInfo culture = CultureInfo.CreateSpecificCulture("en-US");

    private string DecimalValue
    {
        get => decimalValue.ToString("0.000", culture);
        set
        {
            if (Decimal.TryParse(value, style, culture, out var number))
            {
                decimalValue = Math.Round(number, 3);
            }
        }
    }
}
@page "/decimal-binding"
@using System.Globalization

<p>
    <label>
        Decimal value (±0.000 format):
        <input @bind="DecimalValue" />
    </label>
</p>

<p>
    <code>decimalValue</code>: @decimalValue
</p>

@code {
    private decimal decimalValue = 1.1M;
    private NumberStyles style = 
        NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign;
    private CultureInfo culture = CultureInfo.CreateSpecificCulture("en-US");

    private string DecimalValue
    {
        get => decimalValue.ToString("0.000", culture);
        set
        {
            if (Decimal.TryParse(value, style, culture, out var number))
            {
                decimalValue = Math.Round(number, 3);
            }
        }
    }
}
@page "/decimal-binding"
@using System.Globalization

<p>
    <label>
        Decimal value (±0.000 format):
        <input @bind="DecimalValue" />
    </label>
</p>

<p>
    <code>decimalValue</code>: @decimalValue
</p>

@code {
    private decimal decimalValue = 1.1M;
    private NumberStyles style = 
        NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign;
    private CultureInfo culture = CultureInfo.CreateSpecificCulture("en-US");

    private string DecimalValue
    {
        get => decimalValue.ToString("0.000", culture);
        set
        {
            if (Decimal.TryParse(value, style, culture, out var number))
            {
                decimalValue = Math.Round(number, 3);
            }
        }
    }
}

참고 항목

접근자를 사용하여 속성 get/set 에 양방향 바인딩을 수행하려면 반환된 Task EventCallback.InvokeAsync값을 삭제해야 합니다. 양방향 데이터 바인딩의 경우 한정자를 사용하는 @bind:get/@bind:set 것이 좋습니다. 자세한 내용은 이 문서의 앞부분에 있는 지침을 참조 @bind:get/@bind:set 하세요.

참고 항목

접근자를 사용하여 속성 get/set 에 양방향 바인딩을 수행하려면 반환된 Task EventCallback.InvokeAsync값을 삭제해야 합니다. .NET 7 이상에서 ASP.NET Core의 양방향 데이터 바인딩의 경우 이 문서의 7.0 이상 버전에 설명된 한정자를 사용하는 @bind:get/@bind:set 것이 좋습니다.

<select> 요소가 있는 여러 옵션 선택

바인딩은 <select> 요소가 있는 multiple 옵션 선택을 지원합니다. @onchange 이벤트는 이벤트 인수(ChangeEventArgs)를 통해 선택한 요소의 배열을 제공합니다. 값은 배열 형식에 바인딩되어야 합니다.

BindMultipleInput.razor:

@page "/bind-multiple-input"

<h1>Bind Multiple <code>input</code>Example</h1>

<p>
    <label>
        Select one or more cars: 
        <select @onchange="SelectedCarsChanged" multiple>
            <option value="audi">Audi</option>
            <option value="jeep">Jeep</option>
            <option value="opel">Opel</option>
            <option value="saab">Saab</option>
            <option value="volvo">Volvo</option>
        </select>
    </label>
</p>

<p>
    Selected Cars: @string.Join(", ", SelectedCars)
</p>

<p>
    <label>
        Select one or more cities: 
        <select @bind="SelectedCities" multiple>
            <option value="bal">Baltimore</option>
            <option value="la">Los Angeles</option>
            <option value="pdx">Portland</option>
            <option value="sf">San Francisco</option>
            <option value="sea">Seattle</option>
        </select>
    </label>
</p>

<span>
    Selected Cities: @string.Join(", ", SelectedCities)
</span>

@code {
    public string[] SelectedCars { get; set; } = new string[] { };
    public string[] SelectedCities { get; set; } = new[] { "bal", "sea" };

    private void SelectedCarsChanged(ChangeEventArgs e)
    {
        if (e.Value is not null)
        {
            SelectedCars = (string[])e.Value;
        }
    }
}

데이터 바인딩에서 빈 문자열 및 null 값을 처리하는 방법은 C# 개체 null 값에 <select> 요소 옵션 바인딩 섹션을 참조하세요.

C# 개체 null 값에 <select> 요소 옵션 바인딩

다음과 같은 이유로 <select> 요소 옵션 값을 C# 개체 null 값으로 나타낼 수 있는 적절한 방법은 없습니다.

  • HTML 특성에는 null 값을 사용할 수 없습니다. HTML에서 null에 가장 근사한 값은 <option> 요소에서 HTML value 특성이 없는 상태입니다.
  • 특성이 없는 항목을 선택하면 <option> 브라우저에서 해당 값을 해당 <option>요소의 텍스트 콘텐츠처리 value 합니다.

Blazor 프레임워크는 기본 동작에 다음을 포함하므로 기본 동작을 억제하려고 시도하지 않습니다.

  • 프레임워크에서 일련의 특수 경우 해결 방법 만들기
  • 현재 프레임워크 동작에 대한 호환성이 손상되는 변경

HTML에서 가장 그럴 null 듯한 값은 빈 문자열value입니다. Blazor 프레임워크는 <select>의 값에 대한 양방향 바인딩을 위해 null을 빈 문자열 변환으로 처리합니다.

구문 분석할 수 없는 값

사용자가 데이터 바인딩된 요소에 분리할 수 없는 값을 제공하면 바인딩 이벤트가 트리거될 때 분리할 수 없는 값이 자동으로 이전 값으로 되돌아갑니다.

<input> 요소가 초기 값이 123int 형식에 바인딩되는 다음 구성 요소를 고려합니다.

UnparsableValues.razor:

@page "/unparsable-values"

<PageTitle>Unparsable Values</PageTitle>

<h1>Unparsable Values Example</h1>

<p>
    <label>
        inputValue: 
        <input @bind="inputValue" />
    </label>
    
</p>

<p>
    <code>inputValue</code>: @inputValue
</p>

@code {
    private int inputValue = 123;
}
@page "/unparsable-values"

<PageTitle>Unparsable Values</PageTitle>

<h1>Unparsable Values Example</h1>

<p>
    <label>
        inputValue: 
        <input @bind="inputValue" />
    </label>
    
</p>

<p>
    <code>inputValue</code>: @inputValue
</p>

@code {
    private int inputValue = 123;
}
@page "/unparseable-values"

<p>
    <input @bind="inputValue" />
</p>

<p>
    <code>inputValue</code>: @inputValue
</p>

@code {
    private int inputValue = 123;
}
@page "/unparseable-values"

<p>
    <input @bind="inputValue" />
</p>

<p>
    <code>inputValue</code>: @inputValue
</p>

@code {
    private int inputValue = 123;
}
@page "/unparseable-values"

<p>
    <input @bind="inputValue" />
</p>

<p>
    <code>inputValue</code>: @inputValue
</p>

@code {
    private int inputValue = 123;
}
@page "/unparseable-values"

<p>
    <input @bind="inputValue" />
</p>

<p>
    <code>inputValue</code>: @inputValue
</p>

@code {
    private int inputValue = 123;
}

바인딩은 요소의 onchange 이벤트에 적용됩니다. 사용자가 텍스트 상자의 항목 값을 123.45로 업데이트하고 포커스를 변경하는 경우 onchange가 발생하면 요소의 값이 123으로 되돌아갑니다. 값 123.45가 원래 값 123에 따라 거부되면 사용자는 해당 값이 수용되지 않았다는 것을 이해합니다.

oninput 이벤트(@bind:event="oninput")의 경우 구문 분석할 수 없는 값을 도입하는 키 입력 후에 값이 되돌아갑니다. oninput 이벤트의 대상을 int 바인딩 형식으로 지정하는 경우 사용자는 점(.) 문자를 입력할 수 없습니다. 점(.) 문자는 즉시 제거되므로 사용자는 정수만 허용된다는 즉각적인 피드백을 받습니다. 사용자가 구문 분석할 수 없는 <input> 값을 지울 수 있어야 하는 경우와 같이 oninput 이벤트의 값을 되돌리는 것이 적합하지 않은 경우가 있습니다. 대안은 다음과 같습니다.

  • oninput 이벤트를 사용하지 마세요. 요소가 포커스를 잃은 다음에야 잘못된 값을 되돌리는 기본 onchange 이벤트를 사용하세요.
  • int? 또는 string와 같은 null 허용 형식에 바인딩하고 @bind:get/@bind:set 한정자를 사용하거나(이 문서의 앞부분에서 설명한), 잘못된 항목을 처리하기 위해 사용자 지정 getset 접근자 논리를 사용하여 속성에 바인딩합니다.
  • 형식 유효성 검사와 함께 입력 구성 요소(예: InputNumber<TValue> 또는InputDate<TValue>)를 사용합니다. 양식 유효성 검사 구성 요소와 함께 입력 구성 요소는 잘못된 입력을 관리하는 기본 제공 지원을 제공합니다.
    • 사용자가 연결된 EditContext에서 잘못된 입력을 제공하고 유효성 검사 오류를 수신할 수 있도록 허용합니다.
    • 사용자가 추가 Webform 데이터를 입력하는 것을 방해하지 않고 UI에서 유효성 검사 오류를 표시합니다.

형식 문자열

데이터 바인딩은 @bind:format="{FORMAT STRING}"을 사용하는 단일 DateTime 형식 문자열에서 작동합니다. 여기서 {FORMAT STRING} 자리 표시자는 형식 문자열입니다. 통화 또는 숫자 형식과 같은 다른 형식 식은 현재 사용할 수 없지만 이후 릴리스에서 추가될 수 있습니다.

DateBinding.razor:

@page "/date-binding"

<PageTitle>Date Binding</PageTitle>

<h1>Date Binding Example</h1>

<p>
    <label>
        <code>yyyy-MM-dd</code> format:
        <input @bind="startDate" @bind:format="yyyy-MM-dd" />
    </label>
</p>

<p>
    <code>startDate</code>: @startDate
</p>

@code {
    private DateTime startDate = new(2020, 1, 1);
}
@page "/date-binding"

<PageTitle>Date Binding</PageTitle>

<h1>Date Binding Example</h1>

<p>
    <label>
        <code>yyyy-MM-dd</code> format:
        <input @bind="startDate" @bind:format="yyyy-MM-dd" />
    </label>
</p>

<p>
    <code>startDate</code>: @startDate
</p>

@code {
    private DateTime startDate = new(2020, 1, 1);
}
@page "/date-binding"

<p>
    <label>
        <code>yyyy-MM-dd</code> format:
        <input @bind="startDate" @bind:format="yyyy-MM-dd" />
    </label>
</p>

<p>
    <code>startDate</code>: @startDate
</p>

@code {
    private DateTime startDate = new(2020, 1, 1);
}
@page "/date-binding"

<p>
    <label>
        <code>yyyy-MM-dd</code> format:
        <input @bind="startDate" @bind:format="yyyy-MM-dd" />
    </label>
</p>

<p>
    <code>startDate</code>: @startDate
</p>

@code {
    private DateTime startDate = new(2020, 1, 1);
}
@page "/date-binding"

<p>
    <label>
        <code>yyyy-MM-dd</code> format:
        <input @bind="startDate" @bind:format="yyyy-MM-dd" />
    </label>
</p>

<p>
    <code>startDate</code>: @startDate
</p>

@code {
    private DateTime startDate = new(2020, 1, 1);
}
@page "/date-binding"

<p>
    <label>
        <code>yyyy-MM-dd</code> format:
        <input @bind="startDate" @bind:format="yyyy-MM-dd" />
    </label>
</p>

<p>
    <code>startDate</code>: @startDate
</p>

@code {
    private DateTime startDate = new DateTime(2020, 1, 1);
}

위의 코드에서 <input> 요소의 필드 형식(type 특성)은 기본적으로 text입니다.

nullable System.DateTimeSystem.DateTimeOffset이 지원됩니다.

private DateTime? date;
private DateTimeOffset? dateOffset;

Blazor에서는 기본적으로 날짜 형식을 지정할 수 있도록 지원하므로 date 필드의 형식을 지정하는 것은 권장되지 않습니다. 권장 사항에도 불구하고 date 필드의 형식이 제공된 경우 바인딩이 올바르게 작동하려면 yyyy-MM-dd 날짜 형식만 사용합니다.

<input type="date" @bind="startDate" @bind:format="yyyy-MM-dd">

구성 요소 매개 변수를 사용하여 바인딩

일반적인 시나리오에서는 자식 구성 요소의 속성을 부모 구성 요소의 속성에 바인딩합니다. 여러 수준의 바인딩이 동시에 발생하기 때문에 이 시나리오를 체인 바인딩이라고 합니다.

자식 구성 요소에서 구문을 사용하여 연결된 바인딩을 @bind 구현할 수 없습니다. 자식 구성 요소에서 부모의 속성 업데이트를 지원하려면 이벤트 처리기와 값을 별도로 지정해야 합니다. 부모 구성 요소는 여전히 구문을 활용하여 @bind 자식 구성 요소로 데이터 바인딩을 설정합니다.

다음 ChildBind 구성 요소에는 Year 구성 요소 매개 변수와 EventCallback<TValue>이 있습니다. 규칙에 따라 매개 변수에 대한 EventCallback<TValue>의 이름은 “Changed” 접미사를 사용하여 구성 요소 매개 변수 이름으로 지정해야 합니다. 명명 구문은 {PARAMETER NAME}Changed이며 여기서 {PARAMETER NAME} 자리 표시자는 매개 변수 이름입니다. 다음 예제에서 EventCallback<TValue>의 이름은 YearChanged입니다.

EventCallback.InvokeAsync는 제공된 인수를 사용하여 바인딩과 연결된 대리자를 호출하고 변경된 속성에 대한 이벤트 알림을 디스패치합니다.

ChildBind.razor:

<div class="card bg-light mt-3" style="width:18rem ">
    <div class="card-body">
        <h3 class="card-title">ChildBind Component</h3>
        <p class="card-text">
            Child <code>Year</code>: @Year
        </p>
        <button @onclick="UpdateYearFromChild">Update Year from Child</button>
    </div>
</div>

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

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

    private async Task UpdateYearFromChild() => 
        await YearChanged.InvokeAsync(Random.Shared.Next(1950, 2021));
}
<div class="card bg-light mt-3" style="width:18rem ">
    <div class="card-body">
        <h3 class="card-title">ChildBind Component</h3>
        <p class="card-text">
            Child <code>Year</code>: @Year
        </p>
        <button @onclick="UpdateYearFromChild">Update Year from Child</button>
    </div>
</div>

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

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

    private async Task UpdateYearFromChild() => 
        await YearChanged.InvokeAsync(Random.Shared.Next(1950, 2021));
}
<div class="card bg-light mt-3" style="width:18rem ">
    <div class="card-body">
        <h3 class="card-title">ChildBind Component</h3>
        <p class="card-text">
            Child <code>Year</code>: @Year
        </p>
        <button @onclick="UpdateYearFromChild">Update Year from Child</button>
    </div>
</div>

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

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

    private async Task UpdateYearFromChild()
    {
        await YearChanged.InvokeAsync(Random.Shared.Next(1950, 2021));
    }
}
<div class="card bg-light mt-3" style="width:18rem ">
    <div class="card-body">
        <h3 class="card-title">ChildBind Component</h3>
        <p class="card-text">
            Child <code>Year</code>: @Year
        </p>
        <button @onclick="UpdateYearFromChild">Update Year from Child</button>
    </div>
</div>

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

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

    private async Task UpdateYearFromChild()
    {
        await YearChanged.InvokeAsync(Random.Shared.Next(1950, 2021));
    }
}
<div class="card bg-light mt-3" style="width:18rem ">
    <div class="card-body">
        <h3 class="card-title">ChildBind Component</h3>
        <p class="card-text">
            Child <code>Year</code>: @Year
        </p>
        <button @onclick="UpdateYearFromChild">Update Year from Child</button>
    </div>
</div>

@code {
    private Random r = new();

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

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

    private async Task UpdateYearFromChild()
    {
        await YearChanged.InvokeAsync(r.Next(1950, 2021));
    }
}
<div class="card bg-light mt-3" style="width:18rem ">
    <div class="card-body">
        <h3 class="card-title">ChildBind Component</h3>
        <p class="card-text">
            Child <code>Year</code>: @Year
        </p>
        <button @onclick="UpdateYearFromChild">Update Year from Child</button>
    </div>
</div>

@code {
    private Random r = new Random();

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

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

    private async Task UpdateYearFromChild()
    {
        await YearChanged.InvokeAsync(r.Next(1950, 2021));
    }
}

이벤트 및 EventCallback<TValue>에 대한 자세한 내용은 ASP.NET Core Blazor 이벤트 처리 기사의 EventCallback 섹션을 참조하세요.

다음 Parent1 구성 요소에서 year 필드는 자식 구성 요소의 Year 매개 변수에 바인딩되어 있습니다. Year 매개 변수는 Year 매개 변수 형식과 일치하는 도우미 YearChanged 이벤트를 포함하기 때문에 바인딩 가능합니다.

Parent1.razor:

@page "/parent-1"

<PageTitle>Parent 1</PageTitle>

<h1>Parent Example 1</h1>

<p>Parent <code>year</code>: @year</p>

<button @onclick="UpdateYear">Update Parent <code>year</code></button>

<ChildBind @bind-Year="year" />

@code {
    private int year = 1979;

    private void UpdateYear() => year = Random.Shared.Next(1950, 2021);
}
@page "/parent-1"

<PageTitle>Parent 1</PageTitle>

<h1>Parent Example 1</h1>

<p>Parent <code>year</code>: @year</p>

<button @onclick="UpdateYear">Update Parent <code>year</code></button>

<ChildBind @bind-Year="year" />

@code {
    private int year = 1979;

    private void UpdateYear() => year = Random.Shared.Next(1950, 2021);
}
@page "/parent-1"

<h1>Parent Component</h1>

<p>Parent <code>year</code>: @year</p>

<button @onclick="UpdateYear">Update Parent <code>year</code></button>

<ChildBind @bind-Year="year" />

@code {
    private int year = 1979;

    private void UpdateYear()
    {
        year = Random.Shared.Next(1950, 2021);
    }
}
@page "/parent-1"

<h1>Parent Component</h1>

<p>Parent <code>year</code>: @year</p>

<button @onclick="UpdateYear">Update Parent <code>year</code></button>

<ChildBind @bind-Year="year" />

@code {
    private int year = 1979;

    private void UpdateYear()
    {
        year = Random.Shared.Next(1950, 2021);
    }
}
@page "/parent-1"

<h1>Parent Component</h1>

<p>Parent <code>year</code>: @year</p>

<button @onclick="UpdateYear">Update Parent <code>year</code></button>

<ChildBind @bind-Year="year" />

@code {
    private Random r = new();
    private int year = 1979;

    private void UpdateYear()
    {
        year = r.Next(1950, 2021);
    }
}
@page "/parent-1"

<h1>Parent Component</h1>

<p>Parent <code>year</code>: @year</p>

<button @onclick="UpdateYear">Update Parent <code>year</code></button>

<ChildBind @bind-Year="year" />

@code {
    private Random r = new Random();
    private int year = 1979;

    private void UpdateYear()
    {
        year = r.Next(1950, 2021);
    }
}

구성 요소 매개 변수 바인딩은 @bind:after 이벤트를 트리거할 수도 있습니다. 다음 예제에서 YearUpdated 메서드는 Year 구성 요소 매개 변수를 바인딩한 후 비동기적으로 실행됩니다.

<ChildBind @bind-Year="year" @bind-Year:after="YearUpdated" />

@code {
    ...

    private async Task YearUpdated()
    {
        ... = await ...;
    }
}

규칙에 따라 속성은 처리기에 할당된 @bind-{PROPERTY}:event 특성을 포함하여 해당 이벤트 처리기에 바인딩할 수 없습니다. 여기서 {PROPERTY} 자리 표시자는 속성입니다. <ChildBind @bind-Year="year" />는 다음과 같이 작성하는 것과 같습니다.

<ChildBind @bind-Year="year" @bind-Year:event="YearChanged" />

더욱 정교한 실제 예제에서 다음 PasswordEntry 구성 요소는 다음을 수행합니다.

  • <input> 요소의 값을 password 필드로 설정합니다.
  • 자식의 password 필드의 현재 값을 인수로 전달하는 EventCallback을 사용하여 Password 속성의 변경 내용을 부모 구성 요소에 노출합니다.
  • ToggleShowPassword 메서드를 트리거하는 데 onclick 이벤트를 사용합니다. 자세한 내용은 ASP.NET Core Blazor 이벤트 처리를 참조하세요.

Warning

앱 비밀, 연결 문자열, 자격 증명, 암호, PIN(개인 식별 번호), 개인 C#/.NET 코드 또는 프라이빗 키/토큰을 항상 안전하지 않은 클라이언트 쪽 코드에 저장하지 마세요. 테스트/스테이징 및 프로덕션 환경에서 서버 쪽 Blazor 코드 및 웹 API는 프로젝트 코드 또는 구성 파일 내에서 자격 증명을 유지 관리하지 않는 보안 인증 흐름을 사용해야 합니다. 로컬 개발 테스트 외에는 환경 변수가 가장 안전한 방법이 아니므로 환경 변수를 사용하여 중요한 데이터를 저장하는 것을 피하는 것이 좋습니다. 로컬 개발 테스트의 경우 중요한 데이터를 보호하기 위해 Secret Manager 도구를 사용하는 것이 좋습니다. 자세한 내용은 중요한 데이터 및 자격 증명을 안전하게 유지 관리하세요.

PasswordEntry.razor:

<div class="card bg-light mt-3" style="width:22rem ">
    <div class="card-body">
        <h3 class="card-title">Password Component</h3>
        <p class="card-text">
            <label>
                Password:
                <input @oninput="OnPasswordChanged"
                       required
                       type="@(showPassword ? "text" : "password")"
                       value="@password" />
            </label>
        </p>
        <button class="btn btn-primary" @onclick="ToggleShowPassword">
            Show password
        </button>
    </div>
</div>

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

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

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

    private async Task OnPasswordChanged(ChangeEventArgs e)
    {
        password = e?.Value?.ToString();

        await PasswordChanged.InvokeAsync(password);
    }

    private void ToggleShowPassword() => showPassword = !showPassword;
}
<div class="card bg-light mt-3" style="width:22rem ">
    <div class="card-body">
        <h3 class="card-title">Password Component</h3>
        <p class="card-text">
            <label>
                Password:
                <input @oninput="OnPasswordChanged"
                       required
                       type="@(showPassword ? "text" : "password")"
                       value="@password" />
            </label>
        </p>
        <button class="btn btn-primary" @onclick="ToggleShowPassword">
            Show password
        </button>
    </div>
</div>

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

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

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

    private async Task OnPasswordChanged(ChangeEventArgs e)
    {
        password = e?.Value?.ToString();

        await PasswordChanged.InvokeAsync(password);
    }

    private void ToggleShowPassword() => showPassword = !showPassword;
}
<div class="card bg-light mt-3" style="width:22rem ">
    <div class="card-body">
        <h3 class="card-title">Password Component</h3>
        <p class="card-text">
            <label>
                Password:
                <input @oninput="OnPasswordChanged"
                       required
                       type="@(showPassword ? "text" : "password")"
                       value="@password" />
            </label>
        </p>
        <button class="btn btn-primary" @onclick="ToggleShowPassword">
            Show password
        </button>
    </div>
</div>

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

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

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

    private async Task OnPasswordChanged(ChangeEventArgs e)
    {
        password = e?.Value?.ToString();

        await PasswordChanged.InvokeAsync(password);
    }

    private void ToggleShowPassword()
    {
        showPassword = !showPassword;
    }
}
<div class="card bg-light mt-3" style="width:22rem ">
    <div class="card-body">
        <h3 class="card-title">Password Component</h3>
        <p class="card-text">
            <label>
                Password:
                <input @oninput="OnPasswordChanged"
                       required
                       type="@(showPassword ? "text" : "password")"
                       value="@password" />
            </label>
        </p>
        <button class="btn btn-primary" @onclick="ToggleShowPassword">
            Show password
        </button>
    </div>
</div>

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

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

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

    private async Task OnPasswordChanged(ChangeEventArgs e)
    {
        password = e?.Value?.ToString();

        await PasswordChanged.InvokeAsync(password);
    }

    private void ToggleShowPassword()
    {
        showPassword = !showPassword;
    }
}
<div class="card bg-light mt-3" style="width:22rem ">
    <div class="card-body">
        <h3 class="card-title">Password Component</h3>
        <p class="card-text">
            <label>
                Password:
                <input @oninput="OnPasswordChanged"
                       required
                       type="@(showPassword ? "text" : "password")"
                       value="@password" />
            </label>
        </p>
        <button class="btn btn-primary" @onclick="ToggleShowPassword">
            Show password
        </button>
    </div>
</div>

@code {
    private bool showPassword;
    private string password;

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

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

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

        await PasswordChanged.InvokeAsync(password);
    }

    private void ToggleShowPassword()
    {
        showPassword = !showPassword;
    }
}
<div class="card bg-light mt-3" style="width:22rem ">
    <div class="card-body">
        <h3 class="card-title">Password Component</h3>
        <p class="card-text">
            <label>
                Password:
                <input @oninput="OnPasswordChanged"
                       required
                       type="@(showPassword ? "text" : "password")"
                       value="@password" />
            </label>
        </p>
        <button class="btn btn-primary" @onclick="ToggleShowPassword">
            Show password
        </button>
    </div>
</div>

@code {
    private bool showPassword;
    private string password;

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

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

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

        await PasswordChanged.InvokeAsync(password);
    }

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

다음 PasswordBinding 구성 요소 예제와 같이 PasswordEntry 구성 요소는 또 다른 구성 요소에서 사용됩니다.

PasswordBinding.razor:

@page "/password-binding"

<PageTitle>Password Binding</PageTitle>

<h1>Password Binding Example</h1>

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

<p>
    <code>password</code>: @password
</p>

@code {
    private string password = "Not set";
}
@page "/password-binding"

<PageTitle>Password Binding</PageTitle>

<h1>Password Binding Example</h1>

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

<p>
    <code>password</code>: @password
</p>

@code {
    private string password = "Not set";
}
@page "/password-binding"

<h1>Password Binding</h1>

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

<p>
    <code>password</code>: @password
</p>

@code {
    private string password = "Not set";
}
@page "/password-binding"

<h1>Password Binding</h1>

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

<p>
    <code>password</code>: @password
</p>

@code {
    private string password = "Not set";
}
@page "/password-binding"

<h1>Password Binding</h1>

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

<p>
    <code>password</code>: @password
</p>

@code {
    private string password = "Not set";
}
@page "/password-binding"

<h1>Password Binding</h1>

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

<p>
    <code>password</code>: @password
</p>

@code {
    private string password = "Not set";
}

PasswordBinding 구성 요소가 처음 렌더링되면 Not setpassword 값이 UI에 표시됩니다. 초기 렌더링 후 password 값은 PasswordEntry 구성 요소에 Password 구성 요소 매개 변수 값에 대한 변경 내용을 반영합니다.

참고 항목

앞의 예제는 자식 PasswordEntry 구성 요소에서 부모 PasswordBinding 구성 요소로 단방향으로 암호를 바인딩합니다. 앱에 단순히 부모에게 암호를 전달하는 앱에서 다시 사용할 수 있도록 공유 암호 입력 구성 요소를 포함하려는 경우라면 이 시나리오에서 양방향 바인딩은 필요하지 않습니다. 자식 구성 요소의 매개 변수에 직접 쓰지 않고 양방향 바인딩을 허용하는 방법은 이 문서의 셋 이상의 구성 요소에서 바인딩 섹션에 있는 NestedChild 구성 요소 예제를 참조하세요.

처리기에서 검사를 수행하거나 오류를 트래핑합니다. 수정된 다음 PasswordEntry 구성 요소는 암호 값에 공백이 사용되는 경우 사용자에게 즉각적인 피드백을 제공합니다.

PasswordEntry.razor:

<div class="card bg-light mt-3" style="width:22rem ">
    <div class="card-body">
        <h3 class="card-title">Password Component</h3>
        <p class="card-text">
            <label>
                Password:
                <input @oninput="OnPasswordChanged"
                       required
                       type="@(showPassword ? "text" : "password")"
                       value="@password" />
            </label>
            <span class="text-danger">@validationMessage</span>
        </p>
        <button class="btn btn-primary" @onclick="ToggleShowPassword">
            Show password
        </button>
    </div>
</div>

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

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

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

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

        if (password != null && password.Contains(' '))
        {
            validationMessage = "Spaces not allowed!";

            return Task.CompletedTask;
        }
        else
        {
            validationMessage = string.Empty;

            return PasswordChanged.InvokeAsync(password);
        }
    }

    private void ToggleShowPassword() => showPassword = !showPassword;
}
<div class="card bg-light mt-3" style="width:22rem ">
    <div class="card-body">
        <h3 class="card-title">Password Component</h3>
        <p class="card-text">
            <label>
                Password:
                <input @oninput="OnPasswordChanged"
                       required
                       type="@(showPassword ? "text" : "password")"
                       value="@password" />
            </label>
            <span class="text-danger">@validationMessage</span>
        </p>
        <button class="btn btn-primary" @onclick="ToggleShowPassword">
            Show password
        </button>
    </div>
</div>

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

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

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

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

        if (password != null && password.Contains(' '))
        {
            validationMessage = "Spaces not allowed!";

            return Task.CompletedTask;
        }
        else
        {
            validationMessage = string.Empty;

            return PasswordChanged.InvokeAsync(password);
        }
    }

    private void ToggleShowPassword() => showPassword = !showPassword;
}
<div class="card bg-light mt-3" style="width:22rem ">
    <div class="card-body">
        <h3 class="card-title">Password Component</h3>
        <p class="card-text">
            <label>
                Password:
                <input @oninput="OnPasswordChanged"
                       required
                       type="@(showPassword ? "text" : "password")"
                       value="@password" />
            </label>
            <span class="text-danger">@validationMessage</span>
        </p>
        <button class="btn btn-primary" @onclick="ToggleShowPassword">
            Show password
        </button>
    </div>
</div>

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

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

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

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

        if (password != null && password.Contains(' '))
        {
            validationMessage = "Spaces not allowed!";

            return Task.CompletedTask;
        }
        else
        {
            validationMessage = string.Empty;

            return PasswordChanged.InvokeAsync(password);
        }
    }

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

다음 예제에서 PasswordUpdated 메서드는 Password 구성 요소 매개 변수를 바인딩한 후 비동기적으로 실행됩니다.

<PasswordEntry @bind-Password="password" @bind-Password:after="PasswordUpdated" />
<div class="card bg-light mt-3" style="width:22rem ">
    <div class="card-body">
        <h3 class="card-title">Password Component</h3>
        <p class="card-text">
            <label>
                Password:
                <input @oninput="OnPasswordChanged"
                       required
                       type="@(showPassword ? "text" : "password")"
                       value="@password" />
            </label>
            <span class="text-danger">@validationMessage</span>
        </p>
        <button class="btn btn-primary" @onclick="ToggleShowPassword">
            Show password
        </button>
    </div>
</div>

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

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

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

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

        if (password != null && password.Contains(' '))
        {
            validationMessage = "Spaces not allowed!";

            return Task.CompletedTask;
        }
        else
        {
            validationMessage = string.Empty;

            return PasswordChanged.InvokeAsync(password);
        }
    }

    private void ToggleShowPassword()
    {
        showPassword = !showPassword;
    }
}
<div class="card bg-light mt-3" style="width:22rem ">
    <div class="card-body">
        <h3 class="card-title">Password Component</h3>
        <p class="card-text">
            <label>
                Password:
                <input @oninput="OnPasswordChanged"
                       required
                       type="@(showPassword ? "text" : "password")"
                       value="@password" />
            </label>
            <span class="text-danger">@validationMessage</span>
        </p>
        <button class="btn btn-primary" @onclick="ToggleShowPassword">
            Show password
        </button>
    </div>
</div>

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

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

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

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

        if (password.Contains(' '))
        {
            validationMessage = "Spaces not allowed!";

            return Task.CompletedTask;
        }
        else
        {
            validationMessage = string.Empty;

            return PasswordChanged.InvokeAsync(password);
        }
    }

    private void ToggleShowPassword()
    {
        showPassword = !showPassword;
    }
}
<div class="card bg-light mt-3" style="width:22rem ">
    <div class="card-body">
        <h3 class="card-title">Password Component</h3>
        <p class="card-text">
            <label>
                Password:
                <input @oninput="OnPasswordChanged"
                       required
                       type="@(showPassword ? "text" : "password")"
                       value="@password" />
            </label>
            <span class="text-danger">@validationMessage</span>
        </p>
        <button class="btn btn-primary" @onclick="ToggleShowPassword">
            Show password
        </button>
    </div>
</div>

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

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

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

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

        if (password.Contains(' '))
        {
            validationMessage = "Spaces not allowed!";

            return Task.CompletedTask;
        }
        else
        {
            validationMessage = string.Empty;

            return PasswordChanged.InvokeAsync(password);
        }
    }

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

셋 이상의 구성 요소에서 바인딩

많은 중첩된 구성 요소를 통해 매개 변수를 바인딩할 수 있지만 데이터의 단방향 흐름을 준수해야 합니다.

  • 변경 알림은 계층 구조를 따라 올라갑니다.
  • 새 매개 변수 값은 계층 구조를 따라 내려옵니다.

일반적이고 권장되는 방법은 다음 예제와 같이 업데이트할 상태에 대한 혼동을 방지할 수 있도록 부모 구성 요소에 기본 데이터만 저장하는 것입니다.

Parent2.razor:

@page "/parent-2"

<PageTitle>Parent 2</PageTitle>

<h1>Parent Example 2</h1>

<p>Parent Message: <b>@parentMessage</b></p>

<p>
    <button @onclick="ChangeValue">Change from Parent</button>
</p>

<NestedChild @bind-ChildMessage="parentMessage" />

@code {
    private string parentMessage = "Initial value set in Parent";

    private void ChangeValue() => parentMessage = $"Set in Parent {DateTime.Now}";
}
@page "/parent-2"

<PageTitle>Parent 2</PageTitle>

<h1>Parent Example 2</h1>

<p>Parent Message: <b>@parentMessage</b></p>

<p>
    <button @onclick="ChangeValue">Change from Parent</button>
</p>

<NestedChild @bind-ChildMessage="parentMessage" />

@code {
    private string parentMessage = "Initial value set in Parent";

    private void ChangeValue() => parentMessage = $"Set in Parent {DateTime.Now}";
}
@page "/parent-2"

<h1>Parent Component</h1>

<p>Parent Message: <b>@parentMessage</b></p>

<p>
    <button @onclick="ChangeValue">Change from Parent</button>
</p>

<NestedChild @bind-ChildMessage="parentMessage" />

@code {
    private string parentMessage = "Initial value set in Parent";

    private void ChangeValue()
    {
        parentMessage = $"Set in Parent {DateTime.Now}";
    }
}
@page "/parent-2"

<h1>Parent Component</h1>

<p>Parent Message: <b>@parentMessage</b></p>

<p>
    <button @onclick="ChangeValue">Change from Parent</button>
</p>

<NestedChild @bind-ChildMessage="parentMessage" />

@code {
    private string parentMessage = "Initial value set in Parent";

    private void ChangeValue()
    {
        parentMessage = $"Set in Parent {DateTime.Now}";
    }
}
@page "/parent-2"

<h1>Parent Component</h1>

<p>Parent Message: <b>@parentMessage</b></p>

<p>
    <button @onclick="ChangeValue">Change from Parent</button>
</p>

<NestedChild @bind-ChildMessage="parentMessage" />

@code {
    private string parentMessage = "Initial value set in Parent";

    private void ChangeValue()
    {
        parentMessage = $"Set in Parent {DateTime.Now}";
    }
}
@page "/parent-2"

<h1>Parent Component</h1>

<p>Parent Message: <b>@parentMessage</b></p>

<p>
    <button @onclick="ChangeValue">Change from Parent</button>
</p>

<NestedChild @bind-ChildMessage="parentMessage" />

@code {
    private string parentMessage = "Initial value set in Parent";

    private void ChangeValue()
    {
        parentMessage = $"Set in Parent {DateTime.Now}";
    }
}

다음 NestedChild 구성 요소에서 NestedGrandchild 구성 요소는 다음과 같습니다.

  • @bind:get 구문을 사용하여 ChildMessage 값을 GrandchildMessage로 할당합니다.
  • ChildMessageChanged@bind:set 구문을 사용하여 실행되면 GrandchildMessage를 업데이트합니다.

NestedChild.razor:

<div class="border rounded m-1 p-1">
    <h2>Child Component</h2>

    <p>Child Message: <b>@ChildMessage</b></p>

    <p>
        <button @onclick="ChangeValue">Change from Child</button>
    </p>

    <NestedGrandchild @bind-GrandchildMessage:get="ChildMessage" 
        @bind-GrandchildMessage:set="ChildMessageChanged" />
</div>

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

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

    private async Task ChangeValue() => 
        await ChildMessageChanged.InvokeAsync($"Set in Child {DateTime.Now}");
}
<div class="border rounded m-1 p-1">
    <h2>Child Component</h2>

    <p>Child Message: <b>@ChildMessage</b></p>

    <p>
        <button @onclick="ChangeValue">Change from Child</button>
    </p>

    <NestedGrandchild @bind-GrandchildMessage:get="ChildMessage" 
        @bind-GrandchildMessage:set="ChildMessageChanged" />
</div>

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

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

    private async Task ChangeValue() => 
        await ChildMessageChanged.InvokeAsync($"Set in Child {DateTime.Now}");
}
<div class="border rounded m-1 p-1">
    <h2>Child Component</h2>

    <p>Child Message: <b>@ChildMessage</b></p>

    <p>
        <button @onclick="ChangeValue">Change from Child</button>
    </p>

    <NestedGrandchild @bind-GrandchildMessage:get="ChildMessage" 
        @bind-GrandchildMessage:set="ChildMessageChanged" />
</div>

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

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

    private async Task ChangeValue()
    {
        await ChildMessageChanged.InvokeAsync(
            $"Set in Child {DateTime.Now}");
    }
}
<div class="border rounded m-1 p-1">
    <h2>Child Component</h2>

    <p>Child Message: <b>@ChildMessage</b></p>

    <p>
        <button @onclick="ChangeValue">Change from Child</button>
    </p>

    <NestedGrandchild @bind-GrandchildMessage="BoundValue" />
</div>

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

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

    private string BoundValue
    {
        get => ChildMessage ?? string.Empty;
        set => ChildMessageChanged.InvokeAsync(value);
    }

    private async Task ChangeValue()
    {
        await ChildMessageChanged.InvokeAsync(
            $"Set in Child {DateTime.Now}");
    }
}
<div class="border rounded m-1 p-1">
    <h2>Child Component</h2>

    <p>Child Message: <b>@ChildMessage</b></p>

    <p>
        <button @onclick="ChangeValue">Change from Child</button>
    </p>

    <NestedGrandchild @bind-GrandchildMessage="BoundValue" />
</div>

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

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

    private string BoundValue
    {
        get => ChildMessage;
        set => ChildMessageChanged.InvokeAsync(value);
    }

    private async Task ChangeValue()
    {
        await ChildMessageChanged.InvokeAsync(
            $"Set in Child {DateTime.Now}");
    }
}
<div class="border rounded m-1 p-1">
    <h2>Child Component</h2>

    <p>Child Message: <b>@ChildMessage</b></p>

    <p>
        <button @onclick="ChangeValue">Change from Child</button>
    </p>

    <NestedGrandchild @bind-GrandchildMessage="BoundValue" />
</div>

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

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

    private string BoundValue
    {
        get => ChildMessage;
        set => ChildMessageChanged.InvokeAsync(value);
    }

    private async Task ChangeValue()
    {
        await ChildMessageChanged.InvokeAsync(
            $"Set in Child {DateTime.Now}");
    }
}

Warning

일반적으로 자체 구성 요소 매개 변수에 직접 쓰는 구성 요소는 만들지 마세요. 앞의 NestedChild 구성 요소는 ChildMessage 매개 변수에 직접 쓰는 대신 BoundValue 속성을 사용합니다. 자세한 내용은 ASP.NET Core에서 매개 변수 덮어쓰기 방지를 참조 하세요 Blazor.

NestedGrandchild.razor:

<div class="border rounded m-1 p-1">
    <h3>Grandchild Component</h3>

    <p>Grandchild Message: <b>@GrandchildMessage</b></p>

    <p>
        <button @onclick="ChangeValue">Change from Grandchild</button>
    </p>
</div>

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

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

    private async Task ChangeValue() => 
        await GrandchildMessageChanged.InvokeAsync(
            $"Set in Grandchild {DateTime.Now}");
}
<div class="border rounded m-1 p-1">
    <h3>Grandchild Component</h3>

    <p>Grandchild Message: <b>@GrandchildMessage</b></p>

    <p>
        <button @onclick="ChangeValue">Change from Grandchild</button>
    </p>
</div>

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

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

    private async Task ChangeValue() => 
        await GrandchildMessageChanged.InvokeAsync(
            $"Set in Grandchild {DateTime.Now}");
}
<div class="border rounded m-1 p-1">
    <h3>Grandchild Component</h3>

    <p>Grandchild Message: <b>@GrandchildMessage</b></p>

    <p>
        <button @onclick="ChangeValue">Change from Grandchild</button>
    </p>
</div>

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

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

    private async Task ChangeValue()
    {
        await GrandchildMessageChanged.InvokeAsync(
            $"Set in Grandchild {DateTime.Now}");
    }
}
<div class="border rounded m-1 p-1">
    <h3>Grandchild Component</h3>

    <p>Grandchild Message: <b>@GrandchildMessage</b></p>

    <p>
        <button @onclick="ChangeValue">Change from Grandchild</button>
    </p>
</div>

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

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

    private async Task ChangeValue()
    {
        await GrandchildMessageChanged.InvokeAsync(
            $"Set in Grandchild {DateTime.Now}");
    }
}
<div class="border rounded m-1 p-1">
    <h3>Grandchild Component</h3>

    <p>Grandchild Message: <b>@GrandchildMessage</b></p>

    <p>
        <button @onclick="ChangeValue">Change from Grandchild</button>
    </p>
</div>

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

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

    private async Task ChangeValue()
    {
        await GrandchildMessageChanged.InvokeAsync(
            $"Set in Grandchild {DateTime.Now}");
    }
}
<div class="border rounded m-1 p-1">
    <h3>Grandchild Component</h3>

    <p>Grandchild Message: <b>@GrandchildMessage</b></p>

    <p>
        <button @onclick="ChangeValue">Change from Grandchild</button>
    </p>
</div>

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

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

    private async Task ChangeValue()
    {
        await GrandchildMessageChanged.InvokeAsync(
            $"Set in Grandchild {DateTime.Now}");
    }
}

중첩이 필요하지 않은 구성 요소 및 메모리에서 데이터를 공유하는 데 적합한 다른 방법은 ASP.NET Core Blazor 상태 관리를 참조하세요.

바인딩된 필드 또는 속성 식 트리

바인딩 Blazor 과의 보다 심층적인 상호 작용을 용이하게 하기 위해 바인딩된 필드 또는 속성의 식 트리 를 캡처할 수 있습니다. 이 작업은 접미사가 붙는 필드 또는 속성 이름을 사용하여 속성을 정의하여 수행됩니다 Expression. 명명 {FIELD OR PROPERTY NAME}된 지정된 필드 또는 속성의 경우 해당 식 트리 속성의 이름이 지정 {FIELD OR PROPERTY NAME}Expression됩니다.

다음 ChildParameterExpression 구성 요소는 식의 모델 및 필드 이름을 식별합니다 Year . 모델 및 필드 이름을 가져오는 데 사용되는 A FieldIdentifier는 편집할 수 있는 단일 필드를 고유하게 식별합니다. 모델 개체의 속성에 해당하거나 다른 명명된 값일 수 있습니다. 매개 변수 식의 사용은 Microsoft Blazor 설명서에서 다루지 않지만 수많은 타사 리소스에서 처리하는 사용자 지정 유효성 검사 구성 요소를 만들 때 유용합니다.

ChildParameterExpression.razor:

@using System.Linq.Expressions

<ul>
    <li>Year model: @yearField.Model</li>
    <li>Year field name: @yearField.FieldName</li>
</ul>

@code {
    private FieldIdentifier yearField;

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

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

    [Parameter]
    public Expression<Func<int>> YearExpression { get; set; } = default!;

    protected override void OnInitialized() => 
        yearField = FieldIdentifier.Create(YearExpression);
}
@using System.Linq.Expressions

<ul>
    <li>Year model: @yearField.Model</li>
    <li>Year field name: @yearField.FieldName</li>
</ul>

@code {
    private FieldIdentifier yearField;

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

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

    [Parameter]
    public Expression<Func<int>> YearExpression { get; set; } = default!;

    protected override void OnInitialized() => 
        yearField = FieldIdentifier.Create(YearExpression);
}
@using System.Linq.Expressions

<ul>
    <li>Year model: @yearField.Model</li>
    <li>Year field name: @yearField.FieldName</li>
</ul>

@code {
    private FieldIdentifier yearField;

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

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

    [Parameter]
    public Expression<Func<int>> YearExpression { get; set; } = default!;

    protected override void OnInitialized()
    {
        yearField = FieldIdentifier.Create(YearExpression);
    }
}
@using System.Linq.Expressions

<ul>
    <li>Year model: @yearField.Model</li>
    <li>Year field name: @yearField.FieldName</li>
</ul>

@code {
    private FieldIdentifier yearField;

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

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

    [Parameter]
    public Expression<Func<int>> YearExpression { get; set; } = default!;

    protected override void OnInitialized()
    {
        yearField = FieldIdentifier.Create(YearExpression);
    }
}
@using System.Linq.Expressions

<ul>
    <li>Year model: @yearField.Model</li>
    <li>Year field name: @yearField.FieldName</li>
</ul>

@code {
    private FieldIdentifier yearField;

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

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

    [Parameter]
    public Expression<Func<int>> YearExpression { get; set; } = default!;

    protected override void OnInitialized()
    {
        yearField = FieldIdentifier.Create(YearExpression);
    }
}
@using System.Linq.Expressions

<ul>
    <li>Year model: @yearField.Model</li>
    <li>Year field name: @yearField.FieldName</li>
</ul>

@code {
    private FieldIdentifier yearField;

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

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

    [Parameter]
    public Expression<Func<int>> YearExpression { get; set; } = default!;

    protected override void OnInitialized()
    {
        yearField = FieldIdentifier.Create(YearExpression);
    }
}

Parent3.razor:

@page "/parent-3"

<PageTitle>Parent 3</PageTitle>

<h1>Parent Example 3</h1>

<p>Parent <code>year</code>: @year</p>

<ChildParameterExpression @bind-Year="year" />

@code {
    private int year = 1979;
}
@page "/parent-3"

<PageTitle>Parent 3</PageTitle>

<h1>Parent Example 3</h1>

<p>Parent <code>year</code>: @year</p>

<ChildParameterExpression @bind-Year="year" />

@code {
    private int year = 1979;
}
@page "/parent-3"

<h1>Parent Example 3</h1>

<p>Parent <code>year</code>: @year</p>

<ChildParameterExpression @bind-Year="year" />

@code {
    private int year = 1979;
}
@page "/parent-3"

<h1>Parent Example 3</h1>

<p>Parent <code>year</code>: @year</p>

<ChildParameterExpression @bind-Year="year" />

@code {
    private int year = 1979;
}
@page "/parent-3"

<h1>Parent Example 3</h1>

<p>Parent <code>year</code>: @year</p>

<ChildParameterExpression @bind-Year="year" />

@code {
    private int year = 1979;
}
@page "/parent-3"

<h1>Parent Example 3</h1>

<p>Parent <code>year</code>: @year</p>

<ChildParameterExpression @bind-Year="year" />

@code {
    private int year = 1979;
}

추가 리소스