ASP.NET Core Blazor의 .NET 메서드에서 JavaScript 함수 호출
참고 항목
이 문서의 최신 버전은 아닙니다. 현재 릴리스는 이 문서의 .NET 9 버전을 참조 하세요.
Important
이 정보는 상업적으로 출시되기 전에 실질적으로 수정될 수 있는 시험판 제품과 관련이 있습니다. Microsoft는 여기에 제공된 정보에 대해 어떠한 명시적, 또는 묵시적인 보증을 하지 않습니다.
현재 릴리스는 이 문서의 .NET 9 버전을 참조 하세요.
이 문서에서는 .NET에서 JavaScript(JS) 함수를 호출하는 방법을 설명합니다.
JS에서 .NET 메서드를 호출하는 방법에 대한 자세한 내용은 ASP.NET Core Blazor의 JavaScript 함수에서 .NET 메서드 호출을 참조하세요.
JS 함수 호출
IJSRuntime은 Blazor 프레임워크에 의해 등록됩니다. .NET에서 JS를 호출하려면 IJSRuntime 추상화를 삽입하고 다음 메서드 중 하나를 호출합니다.
JS 함수를 호출하는 이전 .NET 메서드의 경우:
- 함수 식별자(
String
)는 전역 범위(window
)를 기준으로 합니다.window.someScope.someFunction
을 호출하려면 식별자가someScope.someFunction
입니다. 호출되기 전에 함수를 등록할 필요는 없습니다. - 함수에 JSON 직렬화 가능 인수
Object[]
를 임의의 수만큼 JS 전달합니다. - 취소 토큰(
CancellationToken
)은 작업을 취소해야 한다는 알림을 전파합니다. TimeSpan
은 JS 작업의 시간 제한을 나타냅니다.- 반환 형식은
TValue
JSON 직렬화 가능해야 합니다.TValue
는 반환되는 JSON 형식에 가장 잘 매핑되는 .NET 형식과 일치해야 합니다. - 메서드에 대해
InvokeAsync
A JSPromise
가 반환됩니다.InvokeAsync
는Promise
의 래핑을 해제하고Promise
가 대기한 값을 반환합니다.
서버 쪽 앱의 기본값인 미리 렌더링을 사용하도록 설정된 앱의 경우 Blazor 미리 렌더링하는 동안 호출 JS 할 수 없습니다. 자세한 내용은 미리 렌더링 섹션을 참조하세요.
다음 예제는 JS 기반 디코더인 TextDecoder
를 기준으로 합니다. 이 예제에서는 C# 메서드에서 개발자 코드의 요구 사항을 기존 JS API로 오프로드하는 JS 함수를 호출하는 방법을 보여 줍니다. JS 함수는 C# 메서드에서 바이트 배열을 수신하고 배열을 디코딩하고 표시를 위해 구성 요소에 텍스트를 반환합니다.
<script>
window.convertArray = (win1251Array) => {
var win1251decoder = new TextDecoder('windows-1251');
var bytes = new Uint8Array(win1251Array);
var decodedArray = win1251decoder.decode(bytes);
return decodedArray;
};
</script>
참고 항목
위치 및 프로덕션 앱에 JS 대한 권장 사항에 대한 일반적인 지침은 ASP.NET Core Blazor 앱의 JavaScript 위치를 참조하세요.
다음 구성 요소는
- 단추(
Convert Array
)를 선택할 때 InvokeAsync를 사용하여convertArray
JS 함수를 호출합니다. - JS 함수를 호출한 후에는 전달된 배열이 문자열로 변환됩니다. 이 문자열은 표시를 위해 구성 요소로 반환됩니다(
text
).
CallJs1.razor
:
@page "/call-js-1"
@inject IJSRuntime JS
<PageTitle>Call JS 1</PageTitle>
<h1>Call JS Example 1</h1>
<p>
<button @onclick="ConvertArray">Convert Array</button>
</p>
<p>
@text
</p>
<p>
Quote ©2005 <a href="https://www.uphe.com">Universal Pictures</a>:
<a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
<a href="https://www.imdb.com/name/nm0472710/">David Krumholtz on IMDB</a>
</p>
@code {
private MarkupString text;
private uint[] quoteArray =
new uint[]
{
60, 101, 109, 62, 67, 97, 110, 39, 116, 32, 115, 116, 111, 112, 32,
116, 104, 101, 32, 115, 105, 103, 110, 97, 108, 44, 32, 77, 97,
108, 46, 60, 47, 101, 109, 62, 32, 45, 32, 77, 114, 46, 32, 85, 110,
105, 118, 101, 114, 115, 101, 10, 10,
};
private async Task ConvertArray() =>
text = new(await JS.InvokeAsync<string>("convertArray", quoteArray));
}
CallJs1.razor
:
@page "/call-js-1"
@inject IJSRuntime JS
<PageTitle>Call JS 1</PageTitle>
<h1>Call JS Example 1</h1>
<p>
<button @onclick="ConvertArray">Convert Array</button>
</p>
<p>
@text
</p>
<p>
Quote ©2005 <a href="https://www.uphe.com">Universal Pictures</a>:
<a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
<a href="https://www.imdb.com/name/nm0472710/">David Krumholtz on IMDB</a>
</p>
@code {
private MarkupString text;
private uint[] quoteArray =
new uint[]
{
60, 101, 109, 62, 67, 97, 110, 39, 116, 32, 115, 116, 111, 112, 32,
116, 104, 101, 32, 115, 105, 103, 110, 97, 108, 44, 32, 77, 97,
108, 46, 60, 47, 101, 109, 62, 32, 45, 32, 77, 114, 46, 32, 85, 110,
105, 118, 101, 114, 115, 101, 10, 10,
};
private async Task ConvertArray() =>
text = new(await JS.InvokeAsync<string>("convertArray", quoteArray));
}
CallJsExample1.razor
:
@page "/call-js-example-1"
@inject IJSRuntime JS
<h1>Call JS <code>convertArray</code> Function</h1>
<p>
<button @onclick="ConvertArray">Convert Array</button>
</p>
<p>
@text
</p>
<p>
Quote ©2005 <a href="https://www.uphe.com">Universal Pictures</a>:
<a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
<a href="https://www.imdb.com/name/nm0472710/">David Krumholtz on IMDB</a>
</p>
@code {
private MarkupString text;
private uint[] quoteArray =
new uint[]
{
60, 101, 109, 62, 67, 97, 110, 39, 116, 32, 115, 116, 111, 112, 32,
116, 104, 101, 32, 115, 105, 103, 110, 97, 108, 44, 32, 77, 97,
108, 46, 60, 47, 101, 109, 62, 32, 45, 32, 77, 114, 46, 32, 85, 110,
105, 118, 101, 114, 115, 101, 10, 10,
};
private async Task ConvertArray()
{
text = new(await JS.InvokeAsync<string>("convertArray", quoteArray));
}
}
CallJsExample1.razor
:
@page "/call-js-example-1"
@inject IJSRuntime JS
<h1>Call JS <code>convertArray</code> Function</h1>
<p>
<button @onclick="ConvertArray">Convert Array</button>
</p>
<p>
@text
</p>
<p>
Quote ©2005 <a href="https://www.uphe.com">Universal Pictures</a>:
<a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
<a href="https://www.imdb.com/name/nm0472710/">David Krumholtz on IMDB</a>
</p>
@code {
private MarkupString text;
private uint[] quoteArray =
new uint[]
{
60, 101, 109, 62, 67, 97, 110, 39, 116, 32, 115, 116, 111, 112, 32,
116, 104, 101, 32, 115, 105, 103, 110, 97, 108, 44, 32, 77, 97,
108, 46, 60, 47, 101, 109, 62, 32, 45, 32, 77, 114, 46, 32, 85, 110,
105, 118, 101, 114, 115, 101, 10, 10,
};
private async Task ConvertArray()
{
text = new(await JS.InvokeAsync<string>("convertArray", quoteArray));
}
}
CallJsExample1.razor
:
@page "/call-js-example-1"
@inject IJSRuntime JS
<h1>Call JS <code>convertArray</code> Function</h1>
<p>
<button @onclick="ConvertArray">Convert Array</button>
</p>
<p>
@text
</p>
<p>
Quote ©2005 <a href="https://www.uphe.com">Universal Pictures</a>:
<a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
<a href="https://www.imdb.com/name/nm0472710/">David Krumholtz on IMDB</a>
</p>
@code {
private MarkupString text;
private uint[] quoteArray =
new uint[]
{
60, 101, 109, 62, 67, 97, 110, 39, 116, 32, 115, 116, 111, 112, 32,
116, 104, 101, 32, 115, 105, 103, 110, 97, 108, 44, 32, 77, 97,
108, 46, 60, 47, 101, 109, 62, 32, 45, 32, 77, 114, 46, 32, 85, 110,
105, 118, 101, 114, 115, 101, 10, 10,
};
private async Task ConvertArray()
{
text = new(await JS.InvokeAsync<string>("convertArray", quoteArray));
}
}
CallJsExample1.razor
:
@page "/call-js-example-1"
@inject IJSRuntime JS
<h1>Call JS <code>convertArray</code> Function</h1>
<p>
<button @onclick="ConvertArray">Convert Array</button>
</p>
<p>
@text
</p>
<p>
Quote ©2005 <a href="https://www.uphe.com">Universal Pictures</a>:
<a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
<a href="https://www.imdb.com/name/nm0472710/">David Krumholtz on IMDB</a>
</p>
@code {
private MarkupString text;
private uint[] quoteArray =
new uint[]
{
60, 101, 109, 62, 67, 97, 110, 39, 116, 32, 115, 116, 111, 112, 32,
116, 104, 101, 32, 115, 105, 103, 110, 97, 108, 44, 32, 77, 97,
108, 46, 60, 47, 101, 109, 62, 32, 45, 32, 77, 114, 46, 32, 85, 110,
105, 118, 101, 114, 115, 101, 10, 10,
};
private async Task ConvertArray()
{
text = new MarkupString(await JS.InvokeAsync<string>("convertArray",
quoteArray));
}
}
사용자 제스처로 제한된 JavaScript API
이 섹션은 서버 쪽 구성 요소에 적용됩니다.
일부 브라우저 JavaScript(JS) API는 Fullscreen API
(MDN 문서) 사용과 같은 사용자 제스처의 컨텍스트에서만 실행할 수 있습니다. 이러한 API는 UI 이벤트 처리가 비동기적으로 수행되고 일반적으로 사용자 제스처의 컨텍스트에서 더 이상 수행되지 않으므로 서버 쪽 구성 요소의 interop 메커니즘을 통해 JS 호출할 수 없습니다. 앱은 JavaScript에서 UI 이벤트를 완전히 처리해야 하므로 Blazor의 @onclick
지시문 특성 대신 onclick
을 사용합니다.
반환된 값(InvokeVoidAsync
)을 읽지 않고 JavaScript 함수를 호출
다음과 같은 경우 InvokeVoidAsync를 사용합니다.
- .NET가 JS(JavaScript) 호출 결과를 읽을 필요가 없는 경우.
- JS 함수가 void(0)/void 0 또는 undefined를 반환하는 경우
displayTickerAlert1
JS 함수를 제공합니다. 이 함수는 InvokeVoidAsync를 사용하여 호출되며 값을 반환하지 않습니다.
<script>
window.displayTickerAlert1 = (symbol, price) => {
alert(`${symbol}: $${price}!`);
};
</script>
참고 항목
위치 및 프로덕션 앱에 JS 대한 권장 사항에 대한 일반적인 지침은 ASP.NET Core Blazor 앱의 JavaScript 위치를 참조하세요.
구성 요소(.razor
) 예제(InvokeVoidAsync
)
TickerChanged
는 다음 구성 요소의 handleTickerChanged1
메서드를 호출합니다.
CallJs2.razor
:
@page "/call-js-2"
@inject IJSRuntime JS
<PageTitle>Call JS 2</PageTitle>
<h1>Call JS Example 2</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@code {
private string? stockSymbol;
private decimal price;
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}" +
$"{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
}
}
CallJs2.razor
:
@page "/call-js-2"
@inject IJSRuntime JS
<PageTitle>Call JS 2</PageTitle>
<h1>Call JS Example 2</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@code {
private string? stockSymbol;
private decimal price;
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}" +
$"{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
}
}
CallJsExample2.razor
:
@page "/call-js-example-2"
@inject IJSRuntime JS
<h1>Call JS Example 2</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@code {
private string? stockSymbol;
private decimal price;
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
}
}
CallJsExample2.razor
:
@page "/call-js-example-2"
@inject IJSRuntime JS
<h1>Call JS Example 2</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@code {
private string? stockSymbol;
private decimal price;
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
}
}
CallJsExample2.razor
:
@page "/call-js-example-2"
@inject IJSRuntime JS
<h1>Call JS Example 2</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@code {
private Random r = new();
private string stockSymbol;
private decimal price;
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
price = r.Next(1, 101);
await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
}
}
CallJsExample2.razor
:
@page "/call-js-example-2"
@inject IJSRuntime JS
<h1>Call JS Example 2</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol != null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@code {
private Random r = new Random();
private string stockSymbol;
private decimal price;
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
price = r.Next(1, 101);
await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
}
}
클래스(.cs
) 예제(InvokeVoidAsync
)
JsInteropClasses1.cs
:
using Microsoft.JSInterop;
namespace BlazorSample;
public class JsInteropClasses1(IJSRuntime js) : IDisposable
{
private readonly IJSRuntime js = js;
public async ValueTask TickerChanged(string symbol, decimal price) =>
await js.InvokeVoidAsync("displayTickerAlert1", symbol, price);
// Calling SuppressFinalize(this) prevents derived types that introduce
// a finalizer from needing to re-implement IDisposable.
public void Dispose() => GC.SuppressFinalize(this);
}
using Microsoft.JSInterop;
namespace BlazorSample;
public class JsInteropClasses1(IJSRuntime js) : IDisposable
{
private readonly IJSRuntime js = js;
public async ValueTask TickerChanged(string symbol, decimal price) =>
await js.InvokeVoidAsync("displayTickerAlert1", symbol, price);
// Calling SuppressFinalize(this) prevents derived types that introduce
// a finalizer from needing to re-implement IDisposable.
public void Dispose() => GC.SuppressFinalize(this);
}
using Microsoft.JSInterop;
public class JsInteropClasses1 : IDisposable
{
private readonly IJSRuntime js;
public JsInteropClasses1(IJSRuntime js)
{
this.js = js;
}
public async ValueTask TickerChanged(string symbol, decimal price)
{
await js.InvokeVoidAsync("displayTickerAlert1", symbol, price);
}
public void Dispose()
{
}
}
using Microsoft.JSInterop;
public class JsInteropClasses1 : IDisposable
{
private readonly IJSRuntime js;
public JsInteropClasses1(IJSRuntime js)
{
this.js = js;
}
public async ValueTask TickerChanged(string symbol, decimal price)
{
await js.InvokeVoidAsync("displayTickerAlert1", symbol, price);
}
public void Dispose()
{
}
}
using System;
using System.Threading.Tasks;
using Microsoft.JSInterop;
public class JsInteropClasses1 : IDisposable
{
private readonly IJSRuntime js;
public JsInteropClasses1(IJSRuntime js)
{
this.js = js;
}
public async ValueTask TickerChanged(string symbol, decimal price)
{
await js.InvokeVoidAsync("displayTickerAlert1", symbol, price);
}
public void Dispose()
{
}
}
using System;
using System.Threading.Tasks;
using Microsoft.JSInterop;
public class JsInteropClasses1 : IDisposable
{
private readonly IJSRuntime js;
public JsInteropClasses1(IJSRuntime js)
{
this.js = js;
}
public async ValueTask TickerChanged(string symbol, decimal price)
{
await js.InvokeVoidAsync("displayTickerAlert1", symbol, price);
}
public void Dispose()
{
}
}
TickerChanged
는 다음 구성 요소의 handleTickerChanged1
메서드를 호출합니다.
CallJs3.razor
:
@page "/call-js-3"
@implements IDisposable
@inject IJSRuntime JS
<PageTitle>Call JS 3</PageTitle>
<h1>Call JS Example 3</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@code {
private string? stockSymbol;
private decimal price;
private JsInteropClasses1? jsClass;
protected override void OnInitialized() => jsClass = new(JS);
private async Task SetStock()
{
if (jsClass is not null)
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}" +
$"{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
await jsClass.TickerChanged(stockSymbol, price);
}
}
public void Dispose() => jsClass?.Dispose();
}
CallJs3.razor
:
@page "/call-js-3"
@implements IDisposable
@inject IJSRuntime JS
<PageTitle>Call JS 3</PageTitle>
<h1>Call JS Example 3</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@code {
private string? stockSymbol;
private decimal price;
private JsInteropClasses1? jsClass;
protected override void OnInitialized() => jsClass = new(JS);
private async Task SetStock()
{
if (jsClass is not null)
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}" +
$"{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
await jsClass.TickerChanged(stockSymbol, price);
}
}
public void Dispose() => jsClass?.Dispose();
}
CallJsExample3.razor
:
@page "/call-js-example-3"
@implements IDisposable
@inject IJSRuntime JS
<h1>Call JS Example 3</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@code {
private string? stockSymbol;
private decimal price;
private JsInteropClasses1? jsClass;
protected override void OnInitialized()
{
jsClass = new(JS);
}
private async Task SetStock()
{
if (jsClass is not null)
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
await jsClass.TickerChanged(stockSymbol, price);
}
}
public void Dispose() => jsClass?.Dispose();
}
CallJsExample3.razor
:
@page "/call-js-example-3"
@implements IDisposable
@inject IJSRuntime JS
<h1>Call JS Example 3</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@code {
private string? stockSymbol;
private decimal price;
private JsInteropClasses1? jsClass;
protected override void OnInitialized()
{
jsClass = new(JS);
}
private async Task SetStock()
{
if (jsClass is not null)
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
await jsClass.TickerChanged(stockSymbol, price);
}
}
public void Dispose() => jsClass?.Dispose();
}
CallJsExample3.razor
:
@page "/call-js-example-3"
@implements IDisposable
@inject IJSRuntime JS
<h1>Call JS Example 3</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@code {
private Random r = new();
private string stockSymbol;
private decimal price;
private JsInteropClasses1 jsClass;
protected override void OnInitialized()
{
jsClass = new(JS);
}
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
price = r.Next(1, 101);
await jsClass.TickerChanged(stockSymbol, price);
}
public void Dispose() => jsClass?.Dispose();
}
CallJsExample3.razor
:
@page "/call-js-example-3"
@implements IDisposable
@inject IJSRuntime JS
<h1>Call JS Example 3</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol != null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@code {
private Random r = new Random();
private string stockSymbol;
private decimal price;
private JsInteropClasses1 jsClass;
protected override void OnInitialized()
{
jsClass = new JsInteropClasses1(JS);
}
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
price = r.Next(1, 101);
await jsClass.TickerChanged(stockSymbol, price);
}
public void Dispose() => jsClass?.Dispose();
}
JavaScript 함수를 호출하고 반환된 값(InvokeAsync
) 읽기
.NET에서 JS(JavaScript) 호출 결과를 읽어야 하는 경우 InvokeAsync를 사용합니다.
displayTickerAlert2
JS 함수를 제공합니다. 다음 예제에서는 호출자가 표시할 문자열을 반환합니다.
<script>
window.displayTickerAlert2 = (symbol, price) => {
if (price < 20) {
alert(`${symbol}: $${price}!`);
return "User alerted in the browser.";
} else {
return "User NOT alerted.";
}
};
</script>
참고 항목
위치 및 프로덕션 앱에 JS 대한 권장 사항에 대한 일반적인 지침은 ASP.NET Core Blazor 앱의 JavaScript 위치를 참조하세요.
구성 요소(.razor
) 예제(InvokeAsync
)
TickerChanged
는 메서드를 handleTickerChanged2
호출하고 반환된 문자열을 다음 구성 요소에 표시합니다.
CallJs4.razor
:
@page "/call-js-4"
@inject IJSRuntime JS
<PageTitle>Call JS 4</PageTitle>
<h1>Call JS Example 4</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@if (result is not null)
{
<p>@result</p>
}
@code {
private string? stockSymbol;
private decimal price;
private string? result;
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}" +
$"{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
var interopResult =
await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol, price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
}
CallJs4.razor
:
@page "/call-js-4"
@inject IJSRuntime JS
<PageTitle>Call JS 4</PageTitle>
<h1>Call JS Example 4</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@if (result is not null)
{
<p>@result</p>
}
@code {
private string? stockSymbol;
private decimal price;
private string? result;
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}" +
$"{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
var interopResult =
await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol, price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
}
CallJsExample4.razor
:
@page "/call-js-example-4"
@inject IJSRuntime JS
<h1>Call JS Example 4</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@if (result is not null)
{
<p>@result</p>
}
@code {
private string? stockSymbol;
private decimal price;
private string? result;
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
var interopResult =
await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol, price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
}
CallJsExample4.razor
:
@page "/call-js-example-4"
@inject IJSRuntime JS
<h1>Call JS Example 4</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@if (result is not null)
{
<p>@result</p>
}
@code {
private string? stockSymbol;
private decimal price;
private string? result;
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
var interopResult =
await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol, price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
}
CallJsExample4.razor
:
@page "/call-js-example-4"
@inject IJSRuntime JS
<h1>Call JS Example 4</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@if (result is not null)
{
<p>@result</p>
}
@code {
private Random r = new();
private string stockSymbol;
private decimal price;
private string result;
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
price = r.Next(1, 101);
var interopResult =
await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol, price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
}
CallJsExample4.razor
:
@page "/call-js-example-4"
@inject IJSRuntime JS
<h1>Call JS Example 4</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol != null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@if (result != null)
{
<p>@result</p>
}
@code {
private Random r = new Random();
private string stockSymbol;
private decimal price;
private string result;
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
price = r.Next(1, 101);
var interopResult =
await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol, price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
}
클래스(.cs
) 예제(InvokeAsync
)
JsInteropClasses2.cs
:
using Microsoft.JSInterop;
namespace BlazorSample;
public class JsInteropClasses2(IJSRuntime js) : IDisposable
{
private readonly IJSRuntime js = js;
public async ValueTask<string> TickerChanged(string symbol, decimal price) =>
await js.InvokeAsync<string>("displayTickerAlert2", symbol, price);
// Calling SuppressFinalize(this) prevents derived types that introduce
// a finalizer from needing to re-implement IDisposable.
public void Dispose() => GC.SuppressFinalize(this);
}
using Microsoft.JSInterop;
namespace BlazorSample;
public class JsInteropClasses2(IJSRuntime js) : IDisposable
{
private readonly IJSRuntime js = js;
public async ValueTask<string> TickerChanged(string symbol, decimal price) =>
await js.InvokeAsync<string>("displayTickerAlert2", symbol, price);
// Calling SuppressFinalize(this) prevents derived types that introduce
// a finalizer from needing to re-implement IDisposable.
public void Dispose() => GC.SuppressFinalize(this);
}
using Microsoft.JSInterop;
public class JsInteropClasses2 : IDisposable
{
private readonly IJSRuntime js;
public JsInteropClasses2(IJSRuntime js)
{
this.js = js;
}
public async ValueTask<string> TickerChanged(string symbol, decimal price)
{
return await js.InvokeAsync<string>("displayTickerAlert2", symbol, price);
}
public void Dispose()
{
}
}
using Microsoft.JSInterop;
public class JsInteropClasses2 : IDisposable
{
private readonly IJSRuntime js;
public JsInteropClasses2(IJSRuntime js)
{
this.js = js;
}
public async ValueTask<string> TickerChanged(string symbol, decimal price)
{
return await js.InvokeAsync<string>("displayTickerAlert2", symbol, price);
}
public void Dispose()
{
}
}
using System;
using System.Threading.Tasks;
using Microsoft.JSInterop;
public class JsInteropClasses2 : IDisposable
{
private readonly IJSRuntime js;
public JsInteropClasses2(IJSRuntime js)
{
this.js = js;
}
public async ValueTask<string> TickerChanged(string symbol, decimal price)
{
return await js.InvokeAsync<string>("displayTickerAlert2", symbol, price);
}
public void Dispose()
{
}
}
using System;
using System.Threading.Tasks;
using Microsoft.JSInterop;
public class JsInteropClasses2 : IDisposable
{
private readonly IJSRuntime js;
public JsInteropClasses2(IJSRuntime js)
{
this.js = js;
}
public async ValueTask<string> TickerChanged(string symbol, decimal price)
{
return await js.InvokeAsync<string>("displayTickerAlert2", symbol, price);
}
public void Dispose()
{
}
}
TickerChanged
는 메서드를 handleTickerChanged2
호출하고 반환된 문자열을 다음 구성 요소에 표시합니다.
CallJs5.razor
:
@page "/call-js-5"
@implements IDisposable
@inject IJSRuntime JS
<PageTitle>Call JS 5</PageTitle>
<h1>Call JS Example 5</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@if (result is not null)
{
<p>@result</p>
}
@code {
private string? stockSymbol;
private decimal price;
private JsInteropClasses2? jsClass;
private string? result;
protected override void OnInitialized() => jsClass = new(JS);
private async Task SetStock()
{
if (jsClass is not null)
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}" +
$"{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
var interopResult = await jsClass.TickerChanged(stockSymbol, price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
}
public void Dispose() => jsClass?.Dispose();
}
CallJs5.razor
:
@page "/call-js-5"
@implements IDisposable
@inject IJSRuntime JS
<PageTitle>Call JS 5</PageTitle>
<h1>Call JS Example 5</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@if (result is not null)
{
<p>@result</p>
}
@code {
private string? stockSymbol;
private decimal price;
private JsInteropClasses2? jsClass;
private string? result;
protected override void OnInitialized() => jsClass = new(JS);
private async Task SetStock()
{
if (jsClass is not null)
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}" +
$"{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
var interopResult = await jsClass.TickerChanged(stockSymbol, price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
}
public void Dispose() => jsClass?.Dispose();
}
CallJsExample5.razor
:
@page "/call-js-example-5"
@implements IDisposable
@inject IJSRuntime JS
<h1>Call JS Example 5</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@if (result is not null)
{
<p>@result</p>
}
@code {
private string? stockSymbol;
private decimal price;
private JsInteropClasses2? jsClass;
private string? result;
protected override void OnInitialized()
{
jsClass = new(JS);
}
private async Task SetStock()
{
if (jsClass is not null)
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
var interopResult = await jsClass.TickerChanged(stockSymbol, price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
}
public void Dispose() => jsClass?.Dispose();
}
CallJsExample5.razor
:
@page "/call-js-example-5"
@implements IDisposable
@inject IJSRuntime JS
<h1>Call JS Example 5</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@if (result is not null)
{
<p>@result</p>
}
@code {
private string? stockSymbol;
private decimal price;
private JsInteropClasses2? jsClass;
private string? result;
protected override void OnInitialized()
{
jsClass = new(JS);
}
private async Task SetStock()
{
if (jsClass is not null)
{
stockSymbol =
$"{(char)('A' + Random.Shared.Next(0, 26))}{(char)('A' + Random.Shared.Next(0, 26))}";
price = Random.Shared.Next(1, 101);
var interopResult = await jsClass.TickerChanged(stockSymbol, price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
}
public void Dispose() => jsClass?.Dispose();
}
CallJsExample5.razor
:
@page "/call-js-example-5"
@implements IDisposable
@inject IJSRuntime JS
<h1>Call JS Example 5</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol is not null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@if (result is not null)
{
<p>@result</p>
}
@code {
private Random r = new();
private string stockSymbol;
private decimal price;
private JsInteropClasses2 jsClass;
private string result;
protected override void OnInitialized()
{
jsClass = new(JS);
}
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
price = r.Next(1, 101);
var interopResult = await jsClass.TickerChanged(stockSymbol, price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
public void Dispose() => jsClass?.Dispose();
}
CallJsExample5.razor
:
@page "/call-js-example-5"
@implements IDisposable
@inject IJSRuntime JS
<h1>Call JS Example 5</h1>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@if (stockSymbol != null)
{
<p>@stockSymbol price: @price.ToString("c")</p>
}
@if (result != null)
{
<p>@result</p>
}
@code {
private Random r = new Random();
private string stockSymbol;
private decimal price;
private JsInteropClasses2 jsClass;
private string result;
protected override void OnInitialized()
{
jsClass = new JsInteropClasses2(JS);
}
private async Task SetStock()
{
stockSymbol =
$"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
price = r.Next(1, 101);
var interopResult = await jsClass.TickerChanged(stockSymbol, price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
public void Dispose() => jsClass?.Dispose();
}
동적 콘텐츠 생성 시나리오
BuildRenderTree를 사용하여 동적 콘텐츠를 생성하려면 [Inject]
특성을 사용합니다.
[Inject]
IJSRuntime JS { get; set; }
미리 렌더링
이 섹션은 구성 요소를 미리 렌더링 Razor 하는 서버 쪽 앱에 적용됩니다. 사전 렌더링은 Prerender ASP.NET Core Razor 구성 요소에서 다룹니다.
이 섹션은 구성 요소를 미리 렌더링 Razor 하는 서버 쪽 앱 및 호스트 Blazor WebAssembly 된 앱에 적용됩니다. 사전 렌더링은 ASP.NET Core Razor 구성 요소 통합에서 다룹니다.
미리 렌더링하는 동안 JavaScript(JS)를 호출할 수 없습니다. 다음 예제에서는 사전 렌더링과 호환되는 방식으로 구성 요소 초기화 논리의 일부로 interop를 사용하는 JS 방법을 보여 줍니다.
다음 scrollElementIntoView
함수는 다음 작업을 수행합니다.
- 를 사용하여 전달된 요소로
scrollIntoView
스크롤합니다. - 메서드에서 요소의
top
속성 값을 반환합니다getBoundingClientRect
.
window.scrollElementIntoView = (element) => {
element.scrollIntoView();
return element.getBoundingClientRect().top;
}
구성 요소 코드 ElementReference 에서 함수를 JS 호출하는 경우 IJSRuntime.InvokeAsync 구성 요소가 렌더링될 때까지 HTML DOM 요소가 없기 때문에 이전의 수명 주기 메서드에서만 사용되고 OnAfterRenderAsync 사용되지 않습니다.
StateHasChanged
(참조 소스)는 interop 호출에서 JS 얻은 새 상태로 구성 요소의 다시 렌더링을 큐에 넣기 위해 호출됩니다(자세한 내용은 ASP.NET Core Razor 구성 요소 렌더링 참조). 무한 루프는 호출될 때만 scrollPosition
null
생성 StateHasChanged 되지 않습니다.
PrerenderedInterop.razor
:
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<PageTitle>Prerendered Interop</PageTitle>
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
앞의 예제에서는 전역 함수를 사용하여 클라이언트를 오염합니다. 프로덕션 앱에서의 더 나은 접근 방식은 JavaScript 모듈에서의 JavaScript 격리를 참조하세요.
클라이언트 쪽 구성 요소의 동기 JS interop
이 섹션은 클라이언트 쪽 구성 요소에만 적용됩니다.
JS interop 호출은 호출된 코드가 동기 또는 비동기인지 여부에 관계없이 비동기입니다. 호출은 구성 요소가 서버 쪽 및 클라이언트 쪽 렌더링 모드에서 호환되도록 비동기적입니다. 서버에서 모든 JS interop 호출은 네트워크 연결을 통해 전송되므로 비동기적이어야 합니다.
구성 요소가 WebAssembly에서만 실행된다는 것을 확실히 알고 있는 경우 동기 JS interop 호출을 하도록 선택할 수 있습니다. 이렇게 하면 비동기 호출을 수행하는 것보다 오버헤드가 약간 감소하며 결과를 기다리는 동안 중간 상태가 없기 때문에 렌더링 주기가 감소할 수 있습니다.
클라이언트 쪽 구성 요소에서 .NET에서 JavaScript로 동기 호출을 수행하려면 interop 호출을 수행 JS 하도록 IJSInProcessRuntime 캐스팅 IJSRuntime 합니다.
@inject IJSRuntime JS
...
@code {
protected override void HandleSomeEvent()
{
var jsInProcess = (IJSInProcessRuntime)JS;
var value = jsInProcess.Invoke<string>("javascriptFunctionIdentifier");
}
}
ASP.NET Core 5.0 이상 클라이언트 쪽 구성 요소에서 작업 IJSObjectReference 하는 경우 대신 동기적으로 사용할 IJSInProcessObjectReference 수 있습니다. IJSInProcessObjectReferenceIAsyncDisposable/IDisposable 는 다음 예제와 같이 메모리 누수 방지를 위해 가비지 수집을 구현하고 삭제해야 합니다.
@inject IJSRuntime JS
@implements IAsyncDisposable
...
@code {
...
private IJSInProcessObjectReference? module;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
module = await JS.InvokeAsync<IJSInProcessObjectReference>("import",
"./scripts.js");
}
}
...
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (module is not null)
{
await module.DisposeAsync();
}
}
}
JavaScript 위치
JavaScript 위치에 대한 문서에서 설명하는 방법을 사용하여 JavaScript(JS) 코드를 로드합니다.
- 태그에
<head>
스크립트 로드(일반적으로 권장되지 않음) <body>
태그에서 스크립트 로드- 구성 요소와 함께 배치된 외부 JavaScript 파일(
.js
)에서 스크립트 로드 - 외부 JavaScript 파일(
.js
)에서 스크립트 로드 - Blazor 시작 전후 스크립트 삽입
- 태그에
<head>
스크립트 로드(일반적으로 권장되지 않음) <body>
태그에서 스크립트 로드- 외부 JavaScript 파일(
.js
)에서 스크립트 로드 - Blazor 시작 후 스크립트 삽입
JS 모듈에서 스크립트를 분리하는 방법에 대한 내용은 Javascript 모듈의 Javascript 격리 섹션을 참조하세요.
Warning
태그를 <script>
동적으로 업데이트할 수 없으므로 구성 요소가 정적 서버 쪽 렌더링(정적 SSR)을 채택하도록 보장되는 경우에만 구성 요소 파일.razor
()에 태그를 <script>
배치합니다.
Warning
<script>
태그를 동적으로 업데이트할 수 없으므로 구성 요소 파일(.razor
)에 <script>
태그를 넣지 마세요.
JavaScript 모듈에서의 JavaScript 격리
Blazor에서는 표준 JavaScript 모듈(ECMAScript 사양)에서 JavaScript(JS) 격리를 사용하도록 설정합니다. JavaScript 모듈 로드는 다른 유형의 웹앱과 Blazor 동일한 방식으로 작동하며, 앱에서 모듈을 정의하는 방법을 자유롭게 사용자 지정할 수 있습니다. JavaScript 모듈을 사용하는 방법에 대한 가이드는 MDN 웹 문서: JavaScript 모듈을 참조 하세요.
JS 격리는 다음과 같은 이점을 제공합니다.
- 가져온 JS는 전역 네임스페이스를 더 이상 오염시키지 않습니다.
- 라이브러리 및 구성 요소의 소비자는 관련 JS를 가져올 필요가 없습니다.
연산자를 사용하여 import()
동적 가져오기는 ASP.NET Core 및 Blazor다음에서 지원됩니다.
if ({CONDITION}) import("/additionalModule.js");
앞의 예제 {CONDITION}
에서 자리 표시자는 모듈을 로드해야 하는지 여부를 결정하는 조건부 검사를 나타냅니다.
브라우저 호환성을 보려면 다음을 사용할 수 있습니다. JavaScript 모듈: 동적 가져오기를 참조하세요.
예를 들어 다음 JS 모듈은 브라우저 창 프롬프트를 표시하기 위해 JS 함수를 내보냅니다. 외부 JS 파일에 다음 JS 코드를 배치합니다.
wwwroot/scripts.js
:
export function showPrompt(message) {
return prompt(message, 'Type anything here');
}
앞의 JS 모듈을 wwwroot
폴더의 정적 웹 자산으로 앱 또는 클래스 라이브러리에 추가한 다음, IJSRuntime 인스턴스에서 InvokeAsync를 호출하여 .NET 코드로 모듈을 가져옵니다.
IJSRuntime은 모듈을 IJSObjectReference로서 가져오는데, 이는 .NET 코드에서 JS 개체에 대한 참조를 나타냅니다. IJSObjectReference를 사용하여 모듈에서 내보낸 JS 함수를 호출합니다.
CallJs6.razor
:
@page "/call-js-6"
@implements IAsyncDisposable
@inject IJSRuntime JS
<PageTitle>Call JS 6</PageTitle>
<h1>Call JS Example 6</h1>
<p>
<button @onclick="TriggerPrompt">Trigger browser window prompt</button>
</p>
<p>
@result
</p>
@code {
private IJSObjectReference? module;
private string? result;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
module = await JS.InvokeAsync<IJSObjectReference>("import",
"./scripts.js");
}
}
private async Task TriggerPrompt() => result = await Prompt("Provide text");
public async ValueTask<string?> Prompt(string message) =>
module is not null ?
await module.InvokeAsync<string>("showPrompt", message) : null;
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (module is not null)
{
await module.DisposeAsync();
}
}
}
CallJs6.razor
:
@page "/call-js-6"
@implements IAsyncDisposable
@inject IJSRuntime JS
<PageTitle>Call JS 6</PageTitle>
<h1>Call JS Example 6</h1>
<p>
<button @onclick="TriggerPrompt">Trigger browser window prompt</button>
</p>
<p>
@result
</p>
@code {
private IJSObjectReference? module;
private string? result;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
module = await JS.InvokeAsync<IJSObjectReference>("import",
"./scripts.js");
}
}
private async Task TriggerPrompt() => result = await Prompt("Provide text");
public async ValueTask<string?> Prompt(string message) =>
module is not null ?
await module.InvokeAsync<string>("showPrompt", message) : null;
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (module is not null)
{
await module.DisposeAsync();
}
}
}
CallJsExample6.razor
:
@page "/call-js-example-6"
@implements IAsyncDisposable
@inject IJSRuntime JS
<h1>Call JS Example 6</h1>
<p>
<button @onclick="TriggerPrompt">Trigger browser window prompt</button>
</p>
<p>
@result
</p>
@code {
private IJSObjectReference? module;
private string? result;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
module = await JS.InvokeAsync<IJSObjectReference>("import",
"./scripts.js");
}
}
private async Task TriggerPrompt()
{
result = await Prompt("Provide some text");
}
public async ValueTask<string?> Prompt(string message) =>
module is not null ?
await module.InvokeAsync<string>("showPrompt", message) : null;
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (module is not null)
{
await module.DisposeAsync();
}
}
}
CallJsExample6.razor
:
@page "/call-js-example-6"
@implements IAsyncDisposable
@inject IJSRuntime JS
<h1>Call JS Example 6</h1>
<p>
<button @onclick="TriggerPrompt">Trigger browser window prompt</button>
</p>
<p>
@result
</p>
@code {
private IJSObjectReference? module;
private string? result;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
module = await JS.InvokeAsync<IJSObjectReference>("import",
"./scripts.js");
}
}
private async Task TriggerPrompt()
{
result = await Prompt("Provide some text");
}
public async ValueTask<string?> Prompt(string message) =>
module is not null ?
await module.InvokeAsync<string>("showPrompt", message) : null;
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (module is not null)
{
await module.DisposeAsync();
}
}
}
CallJsExample6.razor
:
@page "/call-js-example-6"
@implements IAsyncDisposable
@inject IJSRuntime JS
<h1>Call JS Example 6</h1>
<p>
<button @onclick="TriggerPrompt">Trigger browser window prompt</button>
</p>
<p>
@result
</p>
@code {
private IJSObjectReference module;
private string result;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
module = await JS.InvokeAsync<IJSObjectReference>("import",
"./scripts.js");
}
}
private async Task TriggerPrompt()
{
result = await Prompt("Provide some text");
}
public async ValueTask<string> Prompt(string message)
{
return await module.InvokeAsync<string>("showPrompt", message);
}
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (module is not null)
{
await module.DisposeAsync();
}
}
}
앞의 예에서:
- 규칙에 따라,
import
식별자는 JS 모듈을 가져오기 위해 특별히 사용되는 특수 식별자입니다. - 안정적인 정적 웹 자산 경로를 사용하여 모듈의 외부 JS 파일을 지정합니다.
./{SCRIPT PATH AND FILE NAME (.js)}
여기서 다음을 수행합니다.- JS 파일의 올바른 정적 자산 경로를 만들려면 현재 디렉터리의 패스 세그먼트(
./
)가 필요합니다. {SCRIPT PATH AND FILE NAME (.js)}
자리 표시자는wwwroot
아래의 경로 및 파일 이름입니다.
- JS 파일의 올바른 정적 자산 경로를 만들려면 현재 디렉터리의 패스 세그먼트(
- IAsyncDisposable.DisposeAsync에서 가비지 수집을 위해 IJSObjectReference를 삭제합니다.
모듈을 동적으로 가져오려면 네트워크 요청이 필요하므로 InvokeAsync를 호출하여 비동기적으로 수행해야 합니다.
IJSInProcessObjectReference
는 클라이언트 쪽 구성 요소에서 함수를 동기적으로 호출할 수 있는 개체에 대한 참조 JS 를 나타냅니다. 자세한 내용은 클라이언트 쪽 구성 요소의 동기 JS interop 섹션을 참조하세요.
참고 항목
Razor 클래스 라이브러리에서 외부 JS 파일을 제공하는 경우, 안정적인 정적 웹 자산 경로(./_content/{PACKAGE ID}/{SCRIPT PATH AND FILE NAME (.js)}
)를 사용하여 모듈의 JS 파일을 지정합니다.
- JS 파일의 올바른 정적 자산 경로를 만들려면 현재 디렉터리의 패스 세그먼트(
./
)가 필요합니다. {PACKAGE ID}
자리 표시자는 라이브러리의 패키지 ID입니다.<PackageId>
가 프로젝트 파일에 지정되지 않은 경우 패키지 ID는 기본적으로 프로젝트의 어셈블리 이름으로 설정됩니다. 다음 예제에서 라이브러리의 어셈블리 이름은ComponentLibrary
이고 라이브러리의 프로젝트 파일은<PackageId>
를 지정하지 않습니다.{SCRIPT PATH AND FILE NAME (.js)}
자리 표시자는wwwroot
아래의 경로 및 파일 이름입니다. 다음 예제에서는 외부 JS 파일(script.js
)이 클래스 라이브러리의wwwroot
폴더에 배치됩니다.module
는 구성 요소 클래스(private IJSObjectReference? module;
)의 프라이빗 nullable IJSObjectReference 입니다.
module = await js.InvokeAsync<IJSObjectReference>(
"import", "./_content/ComponentLibrary/scripts.js");
자세한 내용은 RCL(Razor 클래스 라이브러리)에서 ASP.NET Core Razor 구성 요소 사용을 참조하세요.
설명서 전체에서 Blazor 예제에서는 최신 .mjs
파일 확장명(RFC 9239)이 아닌 모듈 파일에 파일 확장자를 사용합니다.js
. 이 설명서에서는 Mozilla Foundation 설명서에서 파일 확장자를 계속 사용하는 것과 같은 이유로 파일 확장자를 계속 사용합니다 .js
.js
. 자세한 내용은 Aside ( .mjs 및 .js (MDN 설명서)를 참조하세요.
요소에 대한 참조 캡처
일부 JS(JavaScript) interop 시나리오에는 HTML 요소에 대한 참조가 필요합니다. 예를 들어, UI 라이브러리에는 초기화를 위해 요소 참조가 필요하거나, click
또는 play
와 같은 요소에서 명령 같은 API를 호출해야 할 수 있습니다.
다음 방법을 사용하여 구성 요소의 HTML 요소에 대한 참조를 캡처합니다.
- HTML 요소에
@ref
특성을 추가합니다. - 해당 이름이
@ref
특성 값과 일치하는 ElementReference 형식의 필드를 정의합니다.
다음 예에서는 username
<input>
요소에 대한 참조를 캡처하는 방법을 보여 줍니다.
<input @ref="username" ... />
@code {
private ElementReference username;
}
Warning
요소 참조만 사용하여 Blazor와 상호 작용하지 않는 빈 요소의 내용을 변경합니다. 이 시나리오는 타사 API가 요소에 콘텐츠를 제공하는 경우에 유용합니다. Blazor는 요소와 상호 작용하지 않으므로 요소의 Blazor 표시와 DOM 간에 충돌이 발생할 가능성이 없습니다.
다음 예제에서는 DOM과 상호 작용하여 개체에서 Todos
이 요소의 목록 항목(ul
)을 채우기 때문에 Blazor interop을 통해 MyList
JS 정렬되지 않은 목록(<li>
)의 내용을 변경하면 위험합니다.
<ul @ref="MyList">
@foreach (var item in Todos)
{
<li>@item.Text</li>
}
</ul>
MyList
단지 DOM 콘텐츠를 읽거나 이벤트를 트리거하기 위해 요소 참조를 사용하는 것이 지원됩니다.
JS interop이 요소의 내용을 변경하고 Blazor 요소 MyList
에 diffs를 적용하려고 하면 diffs가 DOM과 일치하지 않습니다. 요소 참조와의 interop을 통해 JS 목록의 내용을 수정하는 MyList
것은 지원되지 않습니다.
자세한 내용은 ASP.NET Core Blazor JavaScript 상호 운용성(JS)을 참조하세요.
ElementReference는 JS interop을 통해 JS 코드로 전달됩니다. JS 코드는 일반적인 DOM API에서 사용할 수 있는 HTMLElement
인스턴스를 수신합니다. 예를 들어 다음 코드는 요소에 마우스 클릭을 전송할 수 있도록 하는 .NET 확장 메서드(TriggerClickEvent
)를 정의합니다.
JS 함수 clickElement
는 전달된 HTML 요소(element
)에 click
이벤트를 만듭니다.
window.interopFunctions = {
clickElement : function (element) {
element.click();
}
}
값을 반환하지 않는 JS 함수를 호출하려면 JSRuntimeExtensions.InvokeVoidAsync를 사용합니다. 다음 코드는 캡처된 ElementReference로 이전 JS 함수를 호출하여 클라이언트 쪽 click
이벤트를 트리거합니다.
@inject IJSRuntime JS
<button @ref="exampleButton">Example Button</button>
<button @onclick="TriggerClick">
Trigger click event on <code>Example Button</code>
</button>
@code {
private ElementReference exampleButton;
public async Task TriggerClick()
{
await JS.InvokeVoidAsync(
"interopFunctions.clickElement", exampleButton);
}
}
확장 메서드를 사용하려면 IJSRuntime 인스턴스를 수신하는 정적 확장 메서드를 만듭니다.
public static async Task TriggerClickEvent(this ElementReference elementRef,
IJSRuntime js)
{
await js.InvokeVoidAsync("interopFunctions.clickElement", elementRef);
}
clickElement
메서드는 개체에서 직접 호출됩니다. 다음 예제에서는 TriggerClickEvent
메서드를 JsInteropClasses
네임스페이스에서 사용할 수 있다고 가정합니다.
@inject IJSRuntime JS
@using JsInteropClasses
<button @ref="exampleButton">Example Button</button>
<button @onclick="TriggerClick">
Trigger click event on <code>Example Button</code>
</button>
@code {
private ElementReference exampleButton;
public async Task TriggerClick()
{
await exampleButton.TriggerClickEvent(JS);
}
}
Important
exampleButton
변수는 구성 요소가 렌더링된 후에만 채워집니다. JS 코드에 채워지지 않은 ElementReference가 전달되면 JS 코드는 null
값을 받습니다. 구성 요소에서 렌더링을 완료한 후에 구성 요소 참조를 조작하려면 OnAfterRenderAsync
또는 OnAfterRender
구성 요소 수명 주기 메서드를 사용합니다.
제네릭 형식으로 작업하고 값을 반환하는 경우 ValueTask<TResult>를 사용합니다.
public static ValueTask<T> GenericMethod<T>(
this ElementReference elementRef, IJSRuntime js) =>
js.InvokeAsync<T>("{JAVASCRIPT FUNCTION}", elementRef);
{JAVASCRIPT FUNCTION}
자리 표시자는 JS 함수 식별자입니다.
GenericMethod
는 형식이 있는 개체에 대해 직접 호출됩니다. 다음 예제에서는 GenericMethod
가 JsInteropClasses
네임스페이스에서 사용할 수 있다고 가정합니다.
@inject IJSRuntime JS
@using JsInteropClasses
<input @ref="username" />
<button @onclick="OnClickMethod">Do something generic</button>
<p>
returnValue: @returnValue
</p>
@code {
private ElementReference username;
private string? returnValue;
private async Task OnClickMethod()
{
returnValue = await username.GenericMethod<string>(JS);
}
}
@inject IJSRuntime JS
@using JsInteropClasses
<input @ref="username" />
<button @onclick="OnClickMethod">Do something generic</button>
<p>
returnValue: @returnValue
</p>
@code {
private ElementReference username;
private string? returnValue;
private async Task OnClickMethod()
{
returnValue = await username.GenericMethod<string>(JS);
}
}
@inject IJSRuntime JS
@using JsInteropClasses
<input @ref="username" />
<button @onclick="OnClickMethod">Do something generic</button>
<p>
returnValue: @returnValue
</p>
@code {
private ElementReference username;
private string returnValue;
private async Task OnClickMethod()
{
returnValue = await username.GenericMethod<string>(JS);
}
}
구성 요소 간 참조 요소
다음과 같은 이유로 구성 요소 간에 ElementReference를 전달할 수 없습니다.
- 인스턴스는 구성 요소가 렌더링된 후 즉, 구성 요소의 OnAfterRender/OnAfterRenderAsync 메서드가 실행되는 동안이나 실행된 후에만 존재하도록 보장됩니다.
- ElementReference는
struct
이며, 구성 요소 매개 변수로 전달할 수 없습니다.
부모 구성 요소는 다른 구성 요소에서 요소 참조를 사용할 수 있도록 하기 위해 다음을 수행할 수 있습니다.
- 자식 구성 요소가 콜백을 등록할 수 있도록 허용합니다.
- 전달된 요소 참조를 사용하여 OnAfterRender 이벤트 중에 등록된 콜백을 호출합니다. 간접적으로 이 방법을 사용하면 자식 구성 요소가 부모의 요소 참조와 상호 작용할 수 있습니다.
<style>
.red { color: red }
</style>
<script>
function setElementClass(element, className) {
var myElement = element;
myElement.classList.add(className);
}
</script>
참고 항목
위치 및 프로덕션 앱에 JS 대한 권장 사항에 대한 일반적인 지침은 ASP.NET Core Blazor 앱의 JavaScript 위치를 참조하세요.
CallJs7.razor
(부모 구성 요소):
@page "/call-js-7"
<PageTitle>Call JS 7</PageTitle>
<h1>Call JS Example 7</h1>
<h2 @ref="title">Hello, world!</h2>
Welcome to your new app.
<SurveyPrompt Parent="this" Title="How is Blazor working for you?" />
CallJs7.razor
(부모 구성 요소):
@page "/call-js-7"
<PageTitle>Call JS 7</PageTitle>
<h1>Call JS Example 7</h1>
<h2 @ref="title">Hello, world!</h2>
Welcome to your new app.
<SurveyPrompt Parent="this" Title="How is Blazor working for you?" />
CallJsExample7.razor
(부모 구성 요소):
@page "/call-js-example-7"
<h1>Call JS Example 7</h1>
<h2 @ref="title">Hello, world!</h2>
Welcome to your new app.
<SurveyPrompt Parent="this" Title="How is Blazor working for you?" />
CallJsExample7.razor
(부모 구성 요소):
@page "/call-js-example-7"
<h1>Call JS Example 7</h1>
<h2 @ref="title">Hello, world!</h2>
Welcome to your new app.
<SurveyPrompt Parent="this" Title="How is Blazor working for you?" />
CallJsExample7.razor
(부모 구성 요소):
@page "/call-js-example-7"
<h1>Call JS Example 7</h1>
<h2 @ref="title">Hello, world!</h2>
Welcome to your new app.
<SurveyPrompt Parent="this" Title="How is Blazor working for you?" />
CallJsExample7.razor
(부모 구성 요소):
@page "/call-js-example-7"
<h1>Call JS Example 7</h1>
<h2 @ref="title">Hello, world!</h2>
Welcome to your new app.
<SurveyPrompt Parent="this" Title="How is Blazor working for you?" />
CallJs7.razor.cs
:
using Microsoft.AspNetCore.Components;
namespace BlazorSample.Pages;
public partial class CallJs7 :
ComponentBase, IObservable<ElementReference>, IDisposable
{
private bool disposing;
private readonly List<IObserver<ElementReference>> subscriptions = [];
private ElementReference title;
protected override void OnAfterRender(bool firstRender)
{
base.OnAfterRender(firstRender);
foreach (var subscription in subscriptions)
{
subscription.OnNext(title);
}
}
public void Dispose()
{
disposing = true;
foreach (var subscription in subscriptions)
{
try
{
subscription.OnCompleted();
}
catch (Exception)
{
}
}
subscriptions.Clear();
// The following prevents derived types that introduce a
// finalizer from needing to re-implement IDisposable.
GC.SuppressFinalize(this);
}
public IDisposable Subscribe(IObserver<ElementReference> observer)
{
if (disposing)
{
throw new InvalidOperationException("Parent being disposed");
}
subscriptions.Add(observer);
return new Subscription(observer, this);
}
private class Subscription(IObserver<ElementReference> observer,
CallJs7 self) : IDisposable
{
public IObserver<ElementReference> Observer { get; } = observer;
public CallJs7 Self { get; } = self;
public void Dispose() => Self.subscriptions.Remove(Observer);
}
}
CallJs7.razor.cs
:
using Microsoft.AspNetCore.Components;
namespace BlazorSample.Pages;
public partial class CallJs7 :
ComponentBase, IObservable<ElementReference>, IDisposable
{
private bool disposing;
private readonly List<IObserver<ElementReference>> subscriptions = [];
private ElementReference title;
protected override void OnAfterRender(bool firstRender)
{
base.OnAfterRender(firstRender);
foreach (var subscription in subscriptions)
{
subscription.OnNext(title);
}
}
public void Dispose()
{
disposing = true;
foreach (var subscription in subscriptions)
{
try
{
subscription.OnCompleted();
}
catch (Exception)
{
}
}
subscriptions.Clear();
// The following prevents derived types that introduce a
// finalizer from needing to re-implement IDisposable.
GC.SuppressFinalize(this);
}
public IDisposable Subscribe(IObserver<ElementReference> observer)
{
if (disposing)
{
throw new InvalidOperationException("Parent being disposed");
}
subscriptions.Add(observer);
return new Subscription(observer, this);
}
private class Subscription(IObserver<ElementReference> observer,
CallJs7 self) : IDisposable
{
public IObserver<ElementReference> Observer { get; } = observer;
public CallJs7 Self { get; } = self;
public void Dispose() => Self.subscriptions.Remove(Observer);
}
}
CallJsExample7.razor.cs
:
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;
namespace BlazorSample.Pages;
public partial class CallJsExample7 :
ComponentBase, IObservable<ElementReference>, IDisposable
{
private bool disposing;
private IList<IObserver<ElementReference>> subscriptions =
new List<IObserver<ElementReference>>();
private ElementReference title;
protected override void OnAfterRender(bool firstRender)
{
base.OnAfterRender(firstRender);
foreach (var subscription in subscriptions)
{
subscription.OnNext(title);
}
}
public void Dispose()
{
disposing = true;
foreach (var subscription in subscriptions)
{
try
{
subscription.OnCompleted();
}
catch (Exception)
{
}
}
subscriptions.Clear();
}
public IDisposable Subscribe(IObserver<ElementReference> observer)
{
if (disposing)
{
throw new InvalidOperationException("Parent being disposed");
}
subscriptions.Add(observer);
return new Subscription(observer, this);
}
private class Subscription : IDisposable
{
public Subscription(IObserver<ElementReference> observer,
CallJsExample7 self)
{
Observer = observer;
Self = self;
}
public IObserver<ElementReference> Observer { get; }
public CallJsExample7 Self { get; }
public void Dispose()
{
Self.subscriptions.Remove(Observer);
}
}
}
CallJsExample7.razor.cs
:
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;
namespace BlazorSample.Pages;
public partial class CallJsExample7 :
ComponentBase, IObservable<ElementReference>, IDisposable
{
private bool disposing;
private IList<IObserver<ElementReference>> subscriptions =
new List<IObserver<ElementReference>>();
private ElementReference title;
protected override void OnAfterRender(bool firstRender)
{
base.OnAfterRender(firstRender);
foreach (var subscription in subscriptions)
{
subscription.OnNext(title);
}
}
public void Dispose()
{
disposing = true;
foreach (var subscription in subscriptions)
{
try
{
subscription.OnCompleted();
}
catch (Exception)
{
}
}
subscriptions.Clear();
}
public IDisposable Subscribe(IObserver<ElementReference> observer)
{
if (disposing)
{
throw new InvalidOperationException("Parent being disposed");
}
subscriptions.Add(observer);
return new Subscription(observer, this);
}
private class Subscription : IDisposable
{
public Subscription(IObserver<ElementReference> observer,
CallJsExample7 self)
{
Observer = observer;
Self = self;
}
public IObserver<ElementReference> Observer { get; }
public CallJsExample7 Self { get; }
public void Dispose()
{
Self.subscriptions.Remove(Observer);
}
}
}
CallJsExample7.razor.cs
:
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;
namespace BlazorSample.Pages
{
public partial class CallJsExample7 :
ComponentBase, IObservable<ElementReference>, IDisposable
{
private bool disposing;
private IList<IObserver<ElementReference>> subscriptions =
new List<IObserver<ElementReference>>();
private ElementReference title;
protected override void OnAfterRender(bool firstRender)
{
base.OnAfterRender(firstRender);
foreach (var subscription in subscriptions)
{
subscription.OnNext(title);
}
}
public void Dispose()
{
disposing = true;
foreach (var subscription in subscriptions)
{
try
{
subscription.OnCompleted();
}
catch (Exception)
{
}
}
subscriptions.Clear();
}
public IDisposable Subscribe(IObserver<ElementReference> observer)
{
if (disposing)
{
throw new InvalidOperationException("Parent being disposed");
}
subscriptions.Add(observer);
return new Subscription(observer, this);
}
private class Subscription : IDisposable
{
public Subscription(IObserver<ElementReference> observer,
CallJsExample7 self)
{
Observer = observer;
Self = self;
}
public IObserver<ElementReference> Observer { get; }
public CallJsExample7 Self { get; }
public void Dispose()
{
Self.subscriptions.Remove(Observer);
}
}
}
}
CallJsExample7.razor.cs
:
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;
namespace BlazorSample.Pages
{
public partial class CallJsExample7 :
ComponentBase, IObservable<ElementReference>, IDisposable
{
private bool disposing;
private IList<IObserver<ElementReference>> subscriptions =
new List<IObserver<ElementReference>>();
private ElementReference title;
protected override void OnAfterRender(bool firstRender)
{
base.OnAfterRender(firstRender);
foreach (var subscription in subscriptions)
{
subscription.OnNext(title);
}
}
public void Dispose()
{
disposing = true;
foreach (var subscription in subscriptions)
{
try
{
subscription.OnCompleted();
}
catch (Exception)
{
}
}
subscriptions.Clear();
}
public IDisposable Subscribe(IObserver<ElementReference> observer)
{
if (disposing)
{
throw new InvalidOperationException("Parent being disposed");
}
subscriptions.Add(observer);
return new Subscription(observer, this);
}
private class Subscription : IDisposable
{
public Subscription(IObserver<ElementReference> observer,
CallJsExample7 self)
{
Observer = observer;
Self = self;
}
public IObserver<ElementReference> Observer { get; }
public CallJsExample7 Self { get; }
public void Dispose()
{
Self.subscriptions.Remove(Observer);
}
}
}
}
앞의 예제에서 앱의 네임스페이스는 .입니다 BlazorSample
. 코드를 로컬로 테스트하는 경우 네임스페이스를 업데이트합니다.
SurveyPrompt.razor
(자식 구성 요소):
<div class="alert alert-secondary mt-4">
<span class="oi oi-pencil me-2" aria-hidden="true"></span>
<strong>@Title</strong>
<span class="text-nowrap">
Please take our
<a target="_blank" class="font-weight-bold link-dark" href="https://go.microsoft.com/fwlink/?linkid=2186158">brief survey</a>
</span>
and tell us what you think.
</div>
@code {
// Demonstrates how a parent component can supply parameters
[Parameter]
public string? Title { get; set; }
}
<div class="alert alert-secondary mt-4">
<span class="oi oi-pencil me-2" aria-hidden="true"></span>
<strong>@Title</strong>
<span class="text-nowrap">
Please take our
<a target="_blank" class="font-weight-bold link-dark" href="https://go.microsoft.com/fwlink/?linkid=2186158">brief survey</a>
</span>
and tell us what you think.
</div>
@code {
// Demonstrates how a parent component can supply parameters
[Parameter]
public string? Title { get; set; }
}
<div class="alert alert-secondary mt-4">
<span class="oi oi-pencil me-2" aria-hidden="true"></span>
<strong>@Title</strong>
<span class="text-nowrap">
Please take our
<a target="_blank" class="font-weight-bold link-dark" href="https://go.microsoft.com/fwlink/?linkid=2186157">brief survey</a>
</span>
and tell us what you think.
</div>
@code {
// Demonstrates how a parent component can supply parameters
[Parameter]
public string? Title { get; set; }
}
<div class="alert alert-secondary mt-4" role="alert">
<span class="oi oi-pencil mr-2" aria-hidden="true"></span>
<strong>@Title</strong>
<span class="text-nowrap">
Please take our
<a target="_blank" class="font-weight-bold"
href="https://go.microsoft.com/fwlink/?linkid=2109206">brief survey</a>
</span>
and tell us what you think.
</div>
@code {
[Parameter]
public string? Title { get; set; }
}
<div class="alert alert-secondary mt-4" role="alert">
<span class="oi oi-pencil mr-2" aria-hidden="true"></span>
<strong>@Title</strong>
<span class="text-nowrap">
Please take our
<a target="_blank" class="font-weight-bold"
href="https://go.microsoft.com/fwlink/?linkid=2109206">brief survey</a>
</span>
and tell us what you think.
</div>
@code {
[Parameter]
public string Title { get; set; }
}
<div class="alert alert-secondary mt-4" role="alert">
<span class="oi oi-pencil mr-2" aria-hidden="true"></span>
<strong>@Title</strong>
<span class="text-nowrap">
Please take our
<a target="_blank" class="font-weight-bold"
href="https://go.microsoft.com/fwlink/?linkid=2109206">brief survey</a>
</span>
and tell us what you think.
</div>
@code {
[Parameter]
public string Title { get; set; }
}
SurveyPrompt.razor.cs
:
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace BlazorSample.Components;
public partial class SurveyPrompt :
ComponentBase, IObserver<ElementReference>, IDisposable
{
private IDisposable? subscription = null;
[Parameter]
public IObservable<ElementReference>? Parent { get; set; }
[Inject]
public IJSRuntime? JS {get; set;}
protected override void OnParametersSet()
{
base.OnParametersSet();
subscription?.Dispose();
subscription = Parent?.Subscribe(this);
}
public void OnCompleted() => subscription = null;
public void OnError(Exception error) => subscription = null;
public void OnNext(ElementReference value) =>
_ = (JS?.InvokeAsync<object>("setElementClass", [value, "red"]));
public void Dispose()
{
subscription?.Dispose();
// The following prevents derived types that introduce a
// finalizer from needing to re-implement IDisposable.
GC.SuppressFinalize(this);
}
}
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace BlazorSample.Components;
public partial class SurveyPrompt :
ComponentBase, IObserver<ElementReference>, IDisposable
{
private IDisposable? subscription = null;
[Parameter]
public IObservable<ElementReference>? Parent { get; set; }
[Inject]
public IJSRuntime? JS {get; set;}
protected override void OnParametersSet()
{
base.OnParametersSet();
subscription?.Dispose();
subscription = Parent?.Subscribe(this);
}
public void OnCompleted() => subscription = null;
public void OnError(Exception error) => subscription = null;
public void OnNext(ElementReference value) =>
_ = (JS?.InvokeAsync<object>("setElementClass", [value, "red"]));
public void Dispose()
{
subscription?.Dispose();
// The following prevents derived types that introduce a
// finalizer from needing to re-implement IDisposable.
GC.SuppressFinalize(this);
}
}
using System;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace BlazorSample.Shared;
public partial class SurveyPrompt :
ComponentBase, IObserver<ElementReference>, IDisposable
{
private IDisposable? subscription = null;
[Parameter]
public IObservable<ElementReference>? Parent { get; set; }
[Inject]
public IJSRuntime? JS {get; set;}
protected override void OnParametersSet()
{
base.OnParametersSet();
subscription?.Dispose();
subscription =
Parent is not null ? Parent.Subscribe(this) : null;
}
public void OnCompleted()
{
subscription = null;
}
public void OnError(Exception error)
{
subscription = null;
}
public void OnNext(ElementReference value)
{
JS.InvokeAsync<object>(
"setElementClass", new object[] { value, "red" });
}
public void Dispose()
{
subscription?.Dispose();
}
}
using System;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace BlazorSample.Shared;
public partial class SurveyPrompt :
ComponentBase, IObserver<ElementReference>, IDisposable
{
private IDisposable? subscription = null;
[Parameter]
public IObservable<ElementReference>? Parent { get; set; }
[Inject]
public IJSRuntime? JS {get; set;}
protected override void OnParametersSet()
{
base.OnParametersSet();
subscription?.Dispose();
subscription =
Parent is not null ? Parent.Subscribe(this) : null;
}
public void OnCompleted()
{
subscription = null;
}
public void OnError(Exception error)
{
subscription = null;
}
public void OnNext(ElementReference value)
{
JS.InvokeAsync<object>(
"setElementClass", new object[] { value, "red" });
}
public void Dispose()
{
subscription?.Dispose();
}
}
using System;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace BlazorSample.Shared
{
public partial class SurveyPrompt :
ComponentBase, IObserver<ElementReference>, IDisposable
{
private IDisposable subscription = null;
[Parameter]
public IObservable<ElementReference> Parent { get; set; }
[Inject]
public IJSRuntime JS {get; set;}
protected override void OnParametersSet()
{
base.OnParametersSet();
subscription?.Dispose();
subscription = Parent.Subscribe(this);
}
public void OnCompleted()
{
subscription = null;
}
public void OnError(Exception error)
{
subscription = null;
}
public void OnNext(ElementReference value)
{
JS.InvokeAsync<object>(
"setElementClass", new object[] { value, "red" });
}
public void Dispose()
{
subscription?.Dispose();
}
}
}
using System;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace BlazorSample.Shared
{
public partial class SurveyPrompt :
ComponentBase, IObserver<ElementReference>, IDisposable
{
private IDisposable subscription = null;
[Parameter]
public IObservable<ElementReference> Parent { get; set; }
[Inject]
public IJSRuntime JS {get; set;}
protected override void OnParametersSet()
{
base.OnParametersSet();
subscription?.Dispose();
subscription = Parent.Subscribe(this);
}
public void OnCompleted()
{
subscription = null;
}
public void OnError(Exception error)
{
subscription = null;
}
public void OnNext(ElementReference value)
{
JS.InvokeAsync<object>(
"setElementClass", new object[] { value, "red" });
}
public void Dispose()
{
subscription?.Dispose();
}
}
}
앞의 예제에서 앱의 네임스페이스는 Shared
폴더에 공유 구성 요소가 있는 BlazorSample
입니다. 코드를 로컬로 테스트하는 경우 네임스페이스를 업데이트합니다.
JavaScript interop 호출 강화
이 섹션은 대화형 서버 구성 요소에만 적용되지만 조건이 보증하는 경우 클라이언트 쪽 구성 요소는 interop 시간 제한을 설정할 JS 수도 있습니다.
서버 대화형 작업이 있는 서버 쪽 앱에서 JavaScript(JS) interop은 네트워킹 오류로 인해 실패할 수 있으며 신뢰할 수 없는 것으로 처리되어야 합니다. Blazor 앱은 interop 호출에 JS 1분 시간 제한을 사용합니다. 앱에서 시간 제한을 좀 더 적극적으로 허용할 수 있는 경우 다음 접근 방식 중 하나를 사용하여 시간 제한을 설정합니다.
CircuitOptions.JSInteropDefaultCallTimeout을 사용하여 Program.cs
에서 전역 시간 제한을 설정합니다.
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents(options =>
options.JSInteropDefaultCallTimeout = {TIMEOUT});
builder.Services.AddServerSideBlazor(
options => options.JSInteropDefaultCallTimeout = {TIMEOUT});
CircuitOptions.JSInteropDefaultCallTimeout을 사용하여 Startup.cs
의 Startup.ConfigureServices
메서드에서 전역 시간 제한을 설정합니다.
services.AddServerSideBlazor(
options => options.JSInteropDefaultCallTimeout = {TIMEOUT});
{TIMEOUT}
자리 표시자는 (예: TimeSpan.FromSeconds(80)
)입니다 TimeSpan .
구성 요소 코드에서 호출당 시간 제한을 설정합니다. 지정된 시간 제한은 JSInteropDefaultCallTimeout에 의해 설정된 전역 시간 제한을 재정의합니다.
var result = await JS.InvokeAsync<string>("{ID}", {TIMEOUT}, new[] { "Arg1" });
앞의 예에서:
{TIMEOUT}
자리 표시자는 (예:TimeSpan.FromSeconds(80)
)입니다 TimeSpan .{ID}
자리 표시자는 호출할 함수의 식별자입니다. 예를 들어someScope.someFunction
값은window.someScope.someFunction
함수를 호출합니다.
interop 오류의 JS 일반적인 원인은 서버 쪽 구성 요소의 네트워크 오류이지만 클라이언트 쪽 구성 요소에 대한 interop 호출에 대해 JS 호출당 시간 제한을 설정할 수 있습니다. 클라이언트 쪽 구성 요소 JS 에 대한 회로가 없 SignalR 더라도 적용되는 다른 이유로 interop 호출이 실패할 수 있습니다.
리소스 소모에 대한 자세한 내용은 ASP.NET Core Blazor 대화형 서버 쪽 렌더링에 대한 위협 완화 지침을 참조하세요.
순환 개체 참조 방지
순환 참조를 포함하는 개체는 다음 중 하나에 대해 클라이언트에서 직렬화될 수 없습니다.
- .NET 메서드 호출
- 반환 형식에 순환 참조가 있는 경우 C#에서 JavaScript 메서드 호출
UI를 렌더링하는 JavaScript 라이브러리
브라우저 DOM 내에서 표시되는 사용자 인터페이스 요소를 생성하는 JavaScript(JS) 라이브러리를 사용할 수도 있습니다. 처음에는, Blazor의 diff 시스템은 DOM 요소의 트리를 제어해야 하는데 일부 외부 코드가 DOM 트리를 변경하고 diff 적용 메커니즘을 무효화하면 오류가 발생하기 때문에 이것이 어려워 보일 수 있습니다. 이는 Blazor 특정 제한 사항이 아닙니다. 동일한 문제가 모든 diff 기반 UI 프레임워크에서 발생합니다.
다행히도 Razor 구성 요소 UI 내에 외부에서 생성된 UI를 안정적으로 포함하는 것은 간단합니다. 구성 요소 코드(.razor
파일)가 빈 요소를 생성하도록 하는 것이 좋습니다. Blazor의 diff 시스템이 관련되는 한 요소는 항상 비어 있으므로 렌더러는 요소로 재귀되지 않으며 대신 콘텐츠를 그대로 둡니다. 이렇게 하면 외부 관리형 임의 콘텐츠로 요소를 안전하게 채울 수 있습니다.
다음 예제에서는 개념을 보여 줍니다. firstRender
가 true
인 경우 if
문 내에서, JS interop을 사용하여 Blazor 외부에서 unmanagedElement
와 상호 작용합니다. 예를 들어 외부 JS 라이브러리를 호출하여 요소를 채웁니다. 이 구성 요소가 제거될 때까지 Blazor는 요소 콘텐츠를 그대로 둡니다. 구성 요소가 제거되면 구성 요소의 전체 DOM 하위 트리도 제거됩니다.
<h1>Hello! This is a Razor component rendered at @DateTime.Now</h1>
<div @ref="unmanagedElement"></div>
@code {
private ElementReference unmanagedElement;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
...
}
}
}
오픈 소스 Mapbox API를 사용하여 대화형 맵을 렌더링하는 다음 예제를 살펴보겠습니다.
다음 JS 모듈은 앱에 배치되거나 Razor 클래스 라이브러리에서 사용할 수 있게 됩니다.
참고 항목
Mapbox 맵을 만들려면 Mapbox Sign in(Mapbox 로그인)에서 액세스 토큰을 가져와, 다음 코드에서 {ACCESS TOKEN}
이 나타나는 곳에 제공합니다.
wwwroot/mapComponent.js
:
import 'https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.js';
mapboxgl.accessToken = '{ACCESS TOKEN}';
export function addMapToElement(element) {
return new mapboxgl.Map({
container: element,
style: 'mapbox://styles/mapbox/streets-v11',
center: [-74.5, 40],
zoom: 9
});
}
export function setMapCenter(map, latitude, longitude) {
map.setCenter([longitude, latitude]);
}
올바른 스타일을 생성하려면 호스트 HTML 페이지에 다음 스타일시트 태그를 추가합니다.
다음 <link>
요소를 <head>
요소 태그(<head>
콘텐츠의 위치)에 추가합니다.
<link href="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css"
rel="stylesheet" />
CallJs8.razor
:
@page "/call-js-8"
@implements IAsyncDisposable
@inject IJSRuntime JS
<PageTitle>Call JS 8</PageTitle>
<HeadContent>
<link href="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css"
rel="stylesheet" />
</HeadContent>
<h1>Call JS Example 8</h1>
<div @ref="mapElement" style='width:400px;height:300px'></div>
<button @onclick="() => ShowAsync(51.454514, -2.587910)">Show Bristol, UK</button>
<button @onclick="() => ShowAsync(35.6762, 139.6503)">Show Tokyo, Japan</button>
@code
{
private ElementReference mapElement;
private IJSObjectReference? mapModule;
private IJSObjectReference? mapInstance;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
mapModule = await JS.InvokeAsync<IJSObjectReference>(
"import", "./mapComponent.js");
mapInstance = await mapModule.InvokeAsync<IJSObjectReference>(
"addMapToElement", mapElement);
}
}
private async Task ShowAsync(double latitude, double longitude)
{
if (mapModule is not null && mapInstance is not null)
{
await mapModule.InvokeVoidAsync("setMapCenter", mapInstance,
latitude, longitude);
}
}
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (mapInstance is not null)
{
await mapInstance.DisposeAsync();
}
if (mapModule is not null)
{
await mapModule.DisposeAsync();
}
}
}
CallJs8.razor
:
@page "/call-js-8"
@implements IAsyncDisposable
@inject IJSRuntime JS
<PageTitle>Call JS 8</PageTitle>
<HeadContent>
<link href="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css"
rel="stylesheet" />
</HeadContent>
<h1>Call JS Example 8</h1>
<div @ref="mapElement" style='width:400px;height:300px'></div>
<button @onclick="() => ShowAsync(51.454514, -2.587910)">Show Bristol, UK</button>
<button @onclick="() => ShowAsync(35.6762, 139.6503)">Show Tokyo, Japan</button>
@code
{
private ElementReference mapElement;
private IJSObjectReference? mapModule;
private IJSObjectReference? mapInstance;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
mapModule = await JS.InvokeAsync<IJSObjectReference>(
"import", "./mapComponent.js");
mapInstance = await mapModule.InvokeAsync<IJSObjectReference>(
"addMapToElement", mapElement);
}
}
private async Task ShowAsync(double latitude, double longitude)
{
if (mapModule is not null && mapInstance is not null)
{
await mapModule.InvokeVoidAsync("setMapCenter", mapInstance,
latitude, longitude);
}
}
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (mapInstance is not null)
{
await mapInstance.DisposeAsync();
}
if (mapModule is not null)
{
await mapModule.DisposeAsync();
}
}
}
CallJsExample8.razor
:
@page "/call-js-example-8"
@implements IAsyncDisposable
@inject IJSRuntime JS
<h1>Call JS Example 8</h1>
<div @ref="mapElement" style='width:400px;height:300px'></div>
<button @onclick="() => ShowAsync(51.454514, -2.587910)">Show Bristol, UK</button>
<button @onclick="() => ShowAsync(35.6762, 139.6503)">Show Tokyo, Japan</button>
@code
{
private ElementReference mapElement;
private IJSObjectReference? mapModule;
private IJSObjectReference? mapInstance;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
mapModule = await JS.InvokeAsync<IJSObjectReference>(
"import", "./mapComponent.js");
mapInstance = await mapModule.InvokeAsync<IJSObjectReference>(
"addMapToElement", mapElement);
}
}
private async Task ShowAsync(double latitude, double longitude)
{
if (mapModule is not null && mapInstance is not null)
{
await mapModule.InvokeVoidAsync("setMapCenter", mapInstance,
latitude, longitude);
}
}
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (mapInstance is not null)
{
await mapInstance.DisposeAsync();
}
if (mapModule is not null)
{
await mapModule.DisposeAsync();
}
}
}
CallJsExample8.razor
:
@page "/call-js-example-8"
@implements IAsyncDisposable
@inject IJSRuntime JS
<h1>Call JS Example 8</h1>
<div @ref="mapElement" style='width:400px;height:300px'></div>
<button @onclick="() => ShowAsync(51.454514, -2.587910)">Show Bristol, UK</button>
<button @onclick="() => ShowAsync(35.6762, 139.6503)">Show Tokyo, Japan</button>
@code
{
private ElementReference mapElement;
private IJSObjectReference? mapModule;
private IJSObjectReference? mapInstance;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
mapModule = await JS.InvokeAsync<IJSObjectReference>(
"import", "./mapComponent.js");
mapInstance = await mapModule.InvokeAsync<IJSObjectReference>(
"addMapToElement", mapElement);
}
}
private async Task ShowAsync(double latitude, double longitude)
{
if (mapModule is not null && mapInstance is not null)
{
await mapModule.InvokeVoidAsync("setMapCenter", mapInstance,
latitude, longitude);
}
}
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (mapInstance is not null)
{
await mapInstance.DisposeAsync();
}
if (mapModule is not null)
{
await mapModule.DisposeAsync();
}
}
}
앞의 예제는 대화형 맵 UI를 생성합니다. 사용자는:
- 끌어서 스크롤하거나 확대/축소할 수 있습니다.
- 단추를 선택하여 미리 정의된 위치로 이동합니다.
앞의 예에서:
- Blazor가 관련되는 한
@ref="mapElement"
가 있는<div>
는 비어 있습니다.mapbox-gl.js
스크립트는 요소를 안전하게 채우고 콘텐츠를 수정할 수 있습니다. UI를 렌더링하는 JS 라이브러리와 함께 이 방법을 사용합니다. 페이지의 다른 부분에 연결하고 해당 부분을 수정하려고 시도하지 않는 한 Razor 구성 요소 내부에 타사 JS SPA 프레임워크의 구성 요소를 포함할 수 있습니다. 외부 JS 코드는 Blazor가 비어 있는 것으로 간주하지 않는 요소를 안전하게 수정할 수 없습니다. - 이 접근 방식을 사용하는 경우 Blazor가 DOM 요소를 유지하거나 제거하는 방법에 대한 규칙에 고려해야 합니다. 가능한 경우 DOM 요소가 유지되므로 구성 요소는 단추 클릭 이벤트를 안전하게 처리하고 기존 맵 인스턴스를 업데이트합니다.
@foreach
루프 내부에서 맵 요소 목록을 렌더링한 경우@key
를 사용하여 구성 요소 인스턴스를 유지할 수 있습니다. 그렇지 않으면 목록 데이터의 변경으로 인해 안타깝게도 구성 요소 인스턴스가 이전 인스턴스의 상태를 유지할 수 있습니다. 자세한 내용은 지시문 특성을 사용하여@key
요소, 구성 요소 및 모델 개체 간의 관계를 유지하는 방법을 참조하세요. - 이 예제에서는 JavaScript 모듈 내의 논리 및 종속성을 캡슐화 JS 하고 식별자를 사용하여 모듈을
import
동적으로 로드합니다. 자세한 내용은 JavaScript 모듈에서의 JavaScript 격리를 참조하세요.
바이트 배열 지원
Blazor는 최적화된 바이트 배열 JS(JavaScript) interop을 지원하여 바이트 배열이 Base64로 인코딩/디코딩되는 것을 방지합니다. 다음 예제에서는 JS interop을 사용하여 바이트 배열을 JavaScript에 전달합니다.
receiveByteArray
JS 함수를 제공합니다. 이 함수는 InvokeVoidAsync를 사용하여 호출되며 값을 반환하지 않습니다.
<script>
window.receiveByteArray = (bytes) => {
let utf8decoder = new TextDecoder();
let str = utf8decoder.decode(bytes);
return str;
};
</script>
참고 항목
위치 및 프로덕션 앱에 JS 대한 권장 사항에 대한 일반적인 지침은 ASP.NET Core Blazor 앱의 JavaScript 위치를 참조하세요.
CallJs9.razor
:
@page "/call-js-9"
@inject IJSRuntime JS
<h1>Call JS Example 9</h1>
<p>
<button @onclick="SendByteArray">Send Bytes</button>
</p>
<p>
@result
</p>
<p>
Quote ©2005 <a href="https://www.uphe.com">Universal Pictures</a>:
<a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
<a href="https://www.imdb.com/name/nm0821612/">Jewel Staite on IMDB</a>
</p>
@code {
private string? result;
private async Task SendByteArray()
{
var bytes = new byte[] { 0x45, 0x76, 0x65, 0x72, 0x79, 0x74, 0x68, 0x69,
0x6e, 0x67, 0x27, 0x73, 0x20, 0x73, 0x68, 0x69, 0x6e, 0x79, 0x2c,
0x20, 0x43, 0x61, 0x70, 0x74, 0x69, 0x61, 0x6e, 0x2e, 0x20, 0x4e,
0x6f, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x66, 0x72, 0x65, 0x74, 0x2e };
result = await JS.InvokeAsync<string>("receiveByteArray", bytes);
}
}
CallJsExample9.razor
:
@page "/call-js-example-9"
@inject IJSRuntime JS
<h1>Call JS Example 9</h1>
<p>
<button @onclick="SendByteArray">Send Bytes</button>
</p>
<p>
@result
</p>
<p>
Quote ©2005 <a href="https://www.uphe.com">Universal Pictures</a>:
<a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
<a href="https://www.imdb.com/name/nm0821612/">Jewel Staite on IMDB</a>
</p>
@code {
private string? result;
private async Task SendByteArray()
{
var bytes = new byte[] { 0x45, 0x76, 0x65, 0x72, 0x79, 0x74, 0x68, 0x69,
0x6e, 0x67, 0x27, 0x73, 0x20, 0x73, 0x68, 0x69, 0x6e, 0x79, 0x2c,
0x20, 0x43, 0x61, 0x70, 0x74, 0x69, 0x61, 0x6e, 0x2e, 0x20, 0x4e,
0x6f, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x66, 0x72, 0x65, 0x74, 0x2e };
result = await JS.InvokeAsync<string>("receiveByteArray", bytes);
}
}
JavaScript에서 .NET을 호출할 때 바이트 배열을 사용하는 방법에 대한 자세한 내용은 ASP.NET Core Blazor의 JavaScript 함수에서 .NET 메서드 호출을 참조하세요.
.NET에서 JavaScript로 스트리밍
Blazor 는 .NET에서 JavaScript(JS)로 직접 스트리밍 데이터를 지원합니다. DotNetStreamReference를 사용하여 스트림이 만들어집니다.
DotNetStreamReference는 .NET 스트림을 나타내며 다음 매개 변수를 사용합니다.
stream
: 에 전송된 스트림입니다 JS.leaveOpen
: 전송 후 스트림을 열어 둘지를 결정합니다. 값을 제공하지 않으면leaveOpen
은 기본적으로false
로 설정됩니다.
에서 JS배열 버퍼 또는 읽을 수 있는 스트림을 사용하여 데이터를 받습니다.
ArrayBuffer
사용:async function streamToJavaScript(streamRef) { const data = await streamRef.arrayBuffer(); }
ReadableStream
사용:async function streamToJavaScript(streamRef) { const stream = await streamRef.stream(); }
C# 코드에서:
var streamRef = new DotNetStreamReference(stream: {STREAM}, leaveOpen: false);
await JS.InvokeVoidAsync("streamToJavaScript", streamRef);
앞의 예에서:
{STREAM}
자리 표시자는 보낸 JS자를 Stream 나타냅니다.JS
는 삽입된 IJSRuntime 인스턴스입니다.
일반적으로 인스턴스 삭제 DotNetStreamReference 는 필요하지 않습니다. 기본값 false
Stream 으로 설정되면 leaveOpen
기본값은 전송 JS후 자동으로 삭제됩니다.
이 true
경우 leaveOpen
삭제해 DotNetStreamReference 도 기본Stream은 삭제되지 않습니다. 앱의 코드는 기본을 삭제할 시기를 결정합니다 Stream. 기본 Stream을 삭제하는 방법을 결정할 때는 다음을 고려합니다.
- 전송 JS 되는 동안 Stream 삭제하는 것은 애플리케이션 오류로 간주되며 처리되지 않은 예외가 발생할 수 있습니다.
- Stream전송은 스트림이 실제로 논리에서 사용되는지 여부에 관계없이 interop 호출에 JS 인수 JS 로 전달되는 즉시 DotNetStreamReference 시작됩니다.
이러한 특성을 고려할 때 기본이 완전히 사용된 JS 후에만 삭제하는 Stream 것이 좋습니다(약속에서 arrayBuffer
반환하거나 stream
해결). 논리에서 DotNetStreamReference 무조건 사용할 JS 경우에만 JS 전달되어야 합니다.
ASP.NET Core Blazor의 JavaScript 함수에서 .NET 메서드 호출에서는 JavaScript에서 .NET으로 스트리밍하는 역방향 작업을 설명합니다.
ASP.NET Core Blazor 파일 다운로드에서는 Blazor에 파일을 다운로드하는 방법을 다룹니다.
JavaScript 예외 Catch
JS 예외를 catch하려면 JS interop을 try
-catch
블록으로 래핑하고 JSException을 catch합니다.
다음 예제에는 nonFunction
JS 함수가 존재하지 않습니다. 함수가 없는 경우에는 JSException이 다음 오류를 나타내는 Message로 트래핑됩니다.
Could not find 'nonFunction' ('nonFunction' was undefined).
CallJs11.razor
:
@page "/call-js-11"
@inject IJSRuntime JS
<PageTitle>Call JS 11</PageTitle>
<h1>Call JS Example 11</h1>
<p>
<button @onclick="CatchUndefinedJSFunction">Catch Exception</button>
</p>
<p>
@result
</p>
<p>
@errorMessage
</p>
@code {
private string? errorMessage;
private string? result;
private async Task CatchUndefinedJSFunction()
{
try
{
result = await JS.InvokeAsync<string>("nonFunction");
}
catch (JSException e)
{
errorMessage = $"Error Message: {e.Message}";
}
}
}
CallJs11.razor
:
@page "/call-js-11"
@inject IJSRuntime JS
<PageTitle>Call JS 11</PageTitle>
<h1>Call JS Example 11</h1>
<p>
<button @onclick="CatchUndefinedJSFunction">Catch Exception</button>
</p>
<p>
@result
</p>
<p>
@errorMessage
</p>
@code {
private string? errorMessage;
private string? result;
private async Task CatchUndefinedJSFunction()
{
try
{
result = await JS.InvokeAsync<string>("nonFunction");
}
catch (JSException e)
{
errorMessage = $"Error Message: {e.Message}";
}
}
}
CallJsExample11.razor
:
@page "/call-js-example-11"
@inject IJSRuntime JS
<h1>Call JS Example 11</h1>
<p>
<button @onclick="CatchUndefinedJSFunction">Catch Exception</button>
</p>
<p>
@result
</p>
<p>
@errorMessage
</p>
@code {
private string? errorMessage;
private string? result;
private async Task CatchUndefinedJSFunction()
{
try
{
result = await JS.InvokeAsync<string>("nonFunction");
}
catch (JSException e)
{
errorMessage = $"Error Message: {e.Message}";
}
}
}
CallJsExample11.razor
:
@page "/call-js-example-11"
@inject IJSRuntime JS
<h1>Call JS Example 11</h1>
<p>
<button @onclick="CatchUndefinedJSFunction">Catch Exception</button>
</p>
<p>
@result
</p>
<p>
@errorMessage
</p>
@code {
private string? errorMessage;
private string? result;
private async Task CatchUndefinedJSFunction()
{
try
{
result = await JS.InvokeAsync<string>("nonFunction");
}
catch (JSException e)
{
errorMessage = $"Error Message: {e.Message}";
}
}
}
CallJsExample11.razor
:
@page "/call-js-example-11"
@inject IJSRuntime JS
<h1>Call JS Example 11</h1>
<p>
<button @onclick="CatchUndefinedJSFunction">Catch Exception</button>
</p>
<p>
@result
</p>
<p>
@errorMessage
</p>
@code {
private string errorMessage;
private string result;
private async Task CatchUndefinedJSFunction()
{
try
{
result = await JS.InvokeAsync<string>("nonFunction");
}
catch (JSException e)
{
errorMessage = $"Error Message: {e.Message}";
}
}
}
CallJsExample11.razor
:
@page "/call-js-example-11"
@inject IJSRuntime JS
<h1>Call JS Example 11</h1>
<p>
<button @onclick="CatchUndefinedJSFunction">Catch Exception</button>
</p>
<p>
@result
</p>
<p>
@errorMessage
</p>
@code {
private string errorMessage;
private string result;
private async Task CatchUndefinedJSFunction()
{
try
{
result = await JS.InvokeAsync<string>("nonFunction");
}
catch (JSException e)
{
errorMessage = $"Error Message: {e.Message}";
}
}
}
장기 실행 JavaScript 함수 중단
JSAbortController를 구성 요소의 CancellationTokenSource와 함께 사용하여 C# 코드에서 장기 실행 JavaScript 함수를 중단합니다.
다음 JSHelpers
클래스에는 장기 실행 함수인 longRunningFn
이 포함되어 있어, AbortController.signal
에 AbortController.abort
가 호출되었다고 표시될 때까지 계속 카운트합니다. sleep
함수는 장기 실행 함수의 느린 실행을 시뮬레이션하기 위한 데모용이며, 프로덕션 코드에는 존재하지 않습니다. 구성 요소에서 stopFn
을 호출하면 longRunningFn
은 AbortSignal.aborted
에서의 while
루프 조건부 검사를 통해 중단 신호를 받습니다.
<script>
class Helpers {
static #controller = new AbortController();
static async #sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
static async longRunningFn() {
var i = 0;
while (!this.#controller.signal.aborted) {
i++;
console.log(`longRunningFn: ${i}`);
await this.#sleep(1000);
}
}
static stopFn() {
this.#controller.abort();
console.log('longRunningFn aborted!');
}
}
window.Helpers = Helpers;
</script>
참고 항목
위치 및 프로덕션 앱에 JS 대한 권장 사항에 대한 일반적인 지침은 ASP.NET Core Blazor 앱의 JavaScript 위치를 참조하세요.
다음 구성 요소는
Start Task
단추가 선택되면 JS 함수longRunningFn
을 호출합니다. CancellationTokenSource는 장기 실행 함수의 실행을 관리하는 데 사용합니다. CancellationToken.Register는 CancellationTokenSource.Token이 취소되면 JS interop 호출 대리자가 JS 함수stopFn
을 실행하도록 설정합니다.Cancel Task
단추가 선택되면 Cancel에 호출하여 CancellationTokenSource.Token이 취소됩니다.- CancellationTokenSource는
Dispose
메서드에서 삭제됩니다.
CallJs12.razor
:
@page "/call-js-12"
@inject IJSRuntime JS
<h1>Cancel long-running JS interop</h1>
<p>
<button @onclick="StartTask">Start Task</button>
<button @onclick="CancelTask">Cancel Task</button>
</p>
@code {
private CancellationTokenSource? cts;
private async Task StartTask()
{
cts = new CancellationTokenSource();
cts.Token.Register(() => JS.InvokeVoidAsync("Helpers.stopFn"));
await JS.InvokeVoidAsync("Helpers.longRunningFn");
}
private void CancelTask()
{
cts?.Cancel();
}
public void Dispose()
{
cts?.Cancel();
cts?.Dispose();
}
}
CallJsExample12.razor
:
@page "/call-js-example-12"
@inject IJSRuntime JS
<h1>Cancel long-running JS interop</h1>
<p>
<button @onclick="StartTask">Start Task</button>
<button @onclick="CancelTask">Cancel Task</button>
</p>
@code {
private CancellationTokenSource? cts;
private async Task StartTask()
{
cts = new CancellationTokenSource();
cts.Token.Register(() => JS.InvokeVoidAsync("Helpers.stopFn"));
await JS.InvokeVoidAsync("Helpers.longRunningFn");
}
private void CancelTask()
{
cts?.Cancel();
}
public void Dispose()
{
cts?.Cancel();
cts?.Dispose();
}
}
브라우저의 개발자 도구 콘솔에는 Start Task
단추가 선택되면 장기 실행 JS 함수의 실행이 표시됩니다. Cancel Task
단추가 선택된 후 함수가 중단되면
longRunningFn: 1
longRunningFn: 2
longRunningFn: 3
longRunningFn aborted!
JavaScript [JSImport]
/[JSExport]
interop
이 섹션은 클라이언트 쪽 구성 요소에 적용됩니다.
인터페이스를 기반으로 IJSRuntime 하는 'interop 메커니즘을 JS 사용하여 Blazor클라이언트 쪽 구성 요소에서 JavaScript(JS)와 상호 작용하는 대신.JS[JSImport]
/[JSExport]
NET 7 이상을 대상으로 하는 앱에서 interop API를 사용할 수 있습니다.
자세한 내용은 ASP.NET Core와의 JavaScript JSImport/JSExport interop를 참조하세요 Blazor.
역마샬링된 JavaScript interop
이 섹션은 클라이언트 쪽 구성 요소에 적용됩니다.
IJSUnmarshalledRuntime인터페이스를 사용하는 역마샬링된 interop는 사용되지 않으며 JavaScript [JSImport]
/[JSExport]
interop로 대체되어야 합니다.
자세한 내용은 ASP.NET Core와의 JavaScript JSImport/JSExport interop를 참조하세요 Blazor.
역마샬링된 JavaScript interop
.NET 개체가 JS(JavaScript) interop에 대해 직렬화되고 다음 중 하나가 참인 경우 Blazor WebAssembly 구성 요소의 성능이 저하될 수 있습니다.
- 많은 양의 .NET 개체가 빠르게 직렬화됩니다. 예를 들어, JS interop 호출이 마우스 휠 회전과 같은 입력 디바이스의 이동을 기반으로 이루어질 경우 성능이 저하될 수 있습니다.
- 많은 양의 .NET 개체 또는 여러 .NET 개체가 JS interop에 대해 직렬화되어야 합니다. 예를 들어, JS interop 호출이 수십 개의 파일을 직렬화해야 하는 경우 성능이 저하될 수 있습니다.
IJSUnmarshalledObjectReference는 .NET 데이터를 직렬화하는 오버헤드 없이 함수를 호출할 수 있는 JS 개체에 대한 참조를 나타냅니다.
다음 예제에서
- 문자열과 정수를 포함하는 구조체가 직렬화되지 않은 상태로 JS로 전달됩니다.
- JS 함수가 데이터를 처리하고 부울 또는 문자열을 호출자에게 반환합니다.
string
문자열은 .NET JS 개체로 직접 변환할 수 없습니다.unmarshalledFunctionReturnString
함수가 JS 문자열의 변환을 관리하기 위해BINDING.js_string_to_mono_string
을 호출합니다.
참고 항목
JS로 전달된 구조체의 결과로 구성 요소 성능이 저하되지 않기 때문에 다음 예제는 이 시나리오의 일반적인 사용 사례가 아닙니다. 예제에서는 직렬화되지 않은 .NET 데이터의 전달이라는 개념을 보여 주기 위한 용도로 작은 개체를 사용합니다.
<script>
window.returnObjectReference = () => {
return {
unmarshalledFunctionReturnBoolean: function (fields) {
const name = Blazor.platform.readStringField(fields, 0);
const year = Blazor.platform.readInt32Field(fields, 8);
return name === "Brigadier Alistair Gordon Lethbridge-Stewart" &&
year === 1968;
},
unmarshalledFunctionReturnString: function (fields) {
const name = Blazor.platform.readStringField(fields, 0);
const year = Blazor.platform.readInt32Field(fields, 8);
return BINDING.js_string_to_mono_string(`Hello, ${name} (${year})!`);
}
};
}
</script>
참고 항목
위치 및 프로덕션 앱에 JS 대한 권장 사항에 대한 일반적인 지침은 ASP.NET Core Blazor 앱의 JavaScript 위치를 참조하세요.
Warning
js_string_to_mono_string
함수 이름, 동작 및 존재는 향후 .NET 릴리스에서 변경될 수 있습니다. 예시:
- 함수의 이름이 바뀔 수 있습니다.
- 함수가 제거되고 프레임워크에 의한 자동 문자열 변환 기능이 사용될 수 있습니다.
CallJsExample10.razor
:
@page "/call-js-example-10"
@using System.Runtime.InteropServices
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Call JS Example 10</h1>
@if (callResultForBoolean)
{
<p>JS interop was successful!</p>
}
@if (!string.IsNullOrEmpty(callResultForString))
{
<p>@callResultForString</p>
}
<p>
<button @onclick="CallJSUnmarshalledForBoolean">
Call Unmarshalled JS & Return Boolean
</button>
<button @onclick="CallJSUnmarshalledForString">
Call Unmarshalled JS & Return String
</button>
</p>
<p>
<a href="https://www.doctorwho.tv">Doctor Who</a>
is a registered trademark of the <a href="https://www.bbc.com/">BBC</a>.
</p>
@code {
private bool callResultForBoolean;
private string? callResultForString;
private void CallJSUnmarshalledForBoolean()
{
var unmarshalledRuntime = (IJSUnmarshalledRuntime)JS;
var jsUnmarshalledReference = unmarshalledRuntime
.InvokeUnmarshalled<IJSUnmarshalledObjectReference>(
"returnObjectReference");
callResultForBoolean =
jsUnmarshalledReference.InvokeUnmarshalled<InteropStruct, bool>(
"unmarshalledFunctionReturnBoolean", GetStruct());
}
private void CallJSUnmarshalledForString()
{
var unmarshalledRuntime = (IJSUnmarshalledRuntime)JS;
var jsUnmarshalledReference = unmarshalledRuntime
.InvokeUnmarshalled<IJSUnmarshalledObjectReference>(
"returnObjectReference");
callResultForString =
jsUnmarshalledReference.InvokeUnmarshalled<InteropStruct, string>(
"unmarshalledFunctionReturnString", GetStruct());
}
private InteropStruct GetStruct()
{
return new InteropStruct
{
Name = "Brigadier Alistair Gordon Lethbridge-Stewart",
Year = 1968,
};
}
[StructLayout(LayoutKind.Explicit)]
public struct InteropStruct
{
[FieldOffset(0)]
public string Name;
[FieldOffset(8)]
public int Year;
}
}
@page "/call-js-example-10"
@using System.Runtime.InteropServices
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Call JS Example 10</h1>
@if (callResultForBoolean)
{
<p>JS interop was successful!</p>
}
@if (!string.IsNullOrEmpty(callResultForString))
{
<p>@callResultForString</p>
}
<p>
<button @onclick="CallJSUnmarshalledForBoolean">
Call Unmarshalled JS & Return Boolean
</button>
<button @onclick="CallJSUnmarshalledForString">
Call Unmarshalled JS & Return String
</button>
</p>
<p>
<a href="https://www.doctorwho.tv">Doctor Who</a>
is a registered trademark of the <a href="https://www.bbc.com/">BBC</a>.
</p>
@code {
private bool callResultForBoolean;
private string callResultForString;
private void CallJSUnmarshalledForBoolean()
{
var unmarshalledRuntime = (IJSUnmarshalledRuntime)JS;
var jsUnmarshalledReference = unmarshalledRuntime
.InvokeUnmarshalled<IJSUnmarshalledObjectReference>(
"returnObjectReference");
callResultForBoolean =
jsUnmarshalledReference.InvokeUnmarshalled<InteropStruct, bool>(
"unmarshalledFunctionReturnBoolean", GetStruct());
}
private void CallJSUnmarshalledForString()
{
var unmarshalledRuntime = (IJSUnmarshalledRuntime)JS;
var jsUnmarshalledReference = unmarshalledRuntime
.InvokeUnmarshalled<IJSUnmarshalledObjectReference>(
"returnObjectReference");
callResultForString =
jsUnmarshalledReference.InvokeUnmarshalled<InteropStruct, string>(
"unmarshalledFunctionReturnString", GetStruct());
}
private InteropStruct GetStruct()
{
return new InteropStruct
{
Name = "Brigadier Alistair Gordon Lethbridge-Stewart",
Year = 1968,
};
}
[StructLayout(LayoutKind.Explicit)]
public struct InteropStruct
{
[FieldOffset(0)]
public string Name;
[FieldOffset(8)]
public int Year;
}
}
IJSUnmarshalledObjectReference 인스턴스가 C# 코드에서 삭제되지 않을 경우 JS에서 삭제될 수 있습니다. 다음 dispose
함수는 JS에서 호출된 경우 개체 참조를 삭제합니다.
window.exampleJSObjectReferenceNotDisposedInCSharp = () => {
return {
dispose: function () {
DotNet.disposeJSObjectReference(this);
},
...
};
}
배열 형식이 js_typed_array_to_array
를 사용하여 JS 개체에서 .NET으로 변환될 수 있지만 JS 배열은 형식화된 배열이어야 합니다. JS로부터의 배열은 C# 코드에서 .NET 개체 배열(object[]
)로 읽힐 수 있습니다.
문자열 배열과 같은 기타 데이터 형식은 변환될 수 있지만 이를 위해서는 새 Mono 배열 개체(mono_obj_array_new
)를 만들고 값을 설정(mono_obj_array_set
)해야 합니다.
Warning
Blazor 프레임워크에서 제공하는 JS 함수(예: js_typed_array_to_array
, mono_obj_array_new
, mono_obj_array_set
)는 .NET의 향후 릴리스에서 이름이 바뀌거나 동작이 변경되거나 제거될 수 있습니다.
JavaScript interop 개체 참조 삭제
JavaScript(JS) interop 문서의 예제는 일반적인 개체 삭제 패턴을 보여 줍니다.
이 문서에 설명된 대로 .NET에서 호출 JS 할 때는 .NET에서 만든/IJSObjectReferenceIJSInProcessObjectReference
JSObjectReference
/ 내용을 삭제하거나 JS 메모리 누수 방지를 위해 삭제합니다.JSASP.NET JSCoreBlazor의 JavaScript 함수에서 .NET 메서드 호출에 설명된 대로 .NET에서 .NET을 호출하는 경우 .NET 메모리 누수 방지를 위해 .NET JS 에서 만든 DotNetObjectReference 메서드를 삭제합니다.
JS interop 개체 참조는 참조를 만드는 interop 호출의 측면에 JS 있는 식별자에 의해 키가 지정된 맵으로 구현됩니다. .NET 또는 JS 쪽 Blazor 에서 개체 삭제가 시작되면 맵에서 항목을 제거하고 개체에 대한 다른 강력한 참조가 없는 한 개체를 가비지 수집할 수 있습니다.
최소한 .NET 관리 메모리 누수 방지를 위해 항상 .NET 쪽에서 만든 개체를 삭제합니다.
구성 요소 삭제 중 DOM 정리 작업
자세한 내용은 ASP.NET Core Blazor JavaScript 상호 운용성(JS)을 참조하세요.
회로가 없는 JavaScript interop 호출
자세한 내용은 ASP.NET Core Blazor JavaScript 상호 운용성(JS)을 참조하세요.
추가 리소스
- ASP.NET Core Blazor의 JavaScript 함수에서 .NET 메서드 호출
InteropComponent.razor
예제(dotnet/AspNetCore
GitHub 리포지토리main
분기): 분기는main
ASP.NET Core의 다음 릴리스에 대한 제품 단위의 현재 개발을 나타냅니다. 다른 릴리스에 대한 분기를 선택하려면(예release/{VERSION}
: 자리 표시자가 릴리스 버전인 경우{VERSION}
) 분기 또는 태그 드롭다운 목록에서 분기를 선택합니다. 더 이상 존재하지 않는 분기의 경우 태그 탭을 사용하여 API(예:v7.0.0
)를 찾습니다.- Blazor샘플 GitHub 리포지토리()(
dotnet/blazor-samples
다운로드 방법) - ASP.NET Core Blazor 앱 에서 오류 처리(JavaScript interop 섹션)
- 위협 완화: .NET에서 호출된 JavaScript 함수
ASP.NET Core