다음을 통해 공유


HybridWebView

.NET 다중 플랫폼 앱 UI(.NET MAUI) HybridWebView 를 사용하면 웹 보기에서 임의의 HTML/JS/CSS 콘텐츠를 호스팅할 수 있으며, 웹 보기(JavaScript)의 코드와 웹 보기(C#/.NET)를 호스트하는 코드 간에 통신할 수 있습니다. 예를 들어 기존 React JS 앱이 있는 경우 플랫폼 간 .NET MAUI 네이티브 앱에서 호스트하고 C# 및 .NET을 사용하여 앱의 백 엔드를 빌드할 수 있습니다.

HybridWebView는 다음 속성을 정의합니다.

  • DefaultFile- 기본 파일로 제공되어야 하는 파일 내의 HybridRoot 파일을 지정하는 형식string?입니다. 기본값은 index.html.
  • HybridRoot- 웹앱의 콘텐츠를 포함하는 앱의 원시 자산 리소스 내 경로인 형식 string?입니다. 기본값은 Resources/Raw/wwwroot에 매핑되는 wwwroot입니다.

또한 HybridWebView 원시 메시지를 받을 때 발생하는 이벤트를 정의 RawMessageReceived 합니다. 이벤트와 함께 제공되는 개체는 HybridWebViewRawMessageReceivedEventArgs 메시지를 포함하는 속성을 정의 Message 합니다.

앱의 C# 코드는 사용 및 메서드 내에서 동기 및 비동기 JavaScript 메서드를 InvokeJavaScriptAsync EvaluateJavaScriptAsync 호출할 HybridWebView 수 있습니다. 자세한 내용은 C#에서 JavaScript 호출을 참조하세요.

.NET MAUI 앱을 HybridWebView 만들려면 다음이 필요합니다.

  • 정적 HTML, JavaScript, CSS, 이미지 및 기타 파일로 구성된 앱의 웹 콘텐츠입니다.
  • HybridWebView 앱 UI의 일부인 컨트롤입니다. 이 작업은 앱의 XAML에서 참조하여 수행할 수 있습니다.
  • API를 사용하여 HybridWebView 두 구성 요소 간에 메시지를 보내는 웹 콘텐츠 및 C#/.NET의 코드입니다.

웹 콘텐츠를 포함한 전체 앱은 패키지되고 디바이스에서 로컬로 실행되며 해당 앱 스토어에 게시할 수 있습니다. 웹 콘텐츠는 네이티브 웹 보기 컨트롤 내에서 호스트되고 앱의 컨텍스트 내에서 실행됩니다. 앱의 모든 부분은 외부 웹 서비스에 액세스할 수 있지만 필요하지는 않습니다.

.NET MAUI HybridWebView 앱 만들기

다음을 사용하여 .NET MAUI 앱을 HybridWebView만들려면

  1. 기존 .NET MAUI 앱 프로젝트를 열거나 새 .NET MAUI 앱 프로젝트를 만듭니다.

  2. .NET MAUI 앱 프로젝트에 웹 콘텐츠를 추가합니다.

    앱의 웹 콘텐츠는 .NET MAUI 프로젝트의 일부로 원시 자산으로 포함되어야 합니다. 원시 자산은 앱의 Resources\Raw 폴더에 있는 파일이며 하위 폴더를 포함합니다. 기본값HybridWebView의 경우 웹 콘텐츠는 Resources\Raw\wwwroot 폴더에 배치되어야 하며 기본 파일은 index.html.

    간단한 앱에는 다음과 같은 파일과 콘텐츠가 있을 수 있습니다.

    • Resources\Raw\wwwroot\index.html 기본 UI에 대한 콘텐츠가 포함됩니다.

      <!DOCTYPE html>
      
      <html lang="en" xmlns="http://www.w3.org/1999/xhtml">
      <head>
          <meta charset="utf-8" />
          <title></title>
          <link rel="icon" href="data:,">
          <script src="scripts/HybridWebView.js"></script>
          <script>
              window.addEventListener(
                  "HybridWebViewMessageReceived",
                  function (e) {
                      var messageFromCSharp = document.getElementById("messageFromCSharp");
                      messageFromCSharp.value += '\r\n' + e.detail.message;
                  });
          </script>
      </head>
      <body>
          <h1>HybridWebView app!</h1>
          <div>
              <button onclick="window.HybridWebView.SendRawMessage('Message from JS!')">Send message to C#</button>
          </div>
          <div>
              Messages from C#: <textarea readonly id="messageFromCSharp" style="width: 80%; height: 300px;"></textarea>
          </div>
      </body>
      </html>
      
    • Resources\Raw\wwwroot\scripts\HybridWebView.js 표준 HybridWebView JavaScript 라이브러리를 사용합니다.

      window.HybridWebView = {
          "Init": function () {
              function DispatchHybridWebViewMessage(message) {
                  const event = new CustomEvent("HybridWebViewMessageReceived", { detail: { message: message } });
                  window.dispatchEvent(event);
              }
      
              if (window.chrome && window.chrome.webview) {
                  // Windows WebView2
                  window.chrome.webview.addEventListener('message', arg => {
                      DispatchHybridWebViewMessage(arg.data);
                  });
              }
              else if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.webwindowinterop) {
                  // iOS and MacCatalyst WKWebView
                  window.external = {
                      "receiveMessage": message => {
                          DispatchHybridWebViewMessage(message);
                      }
                  };
              }
              else {
                  // Android WebView
                  window.addEventListener('message', arg => {
                      DispatchHybridWebViewMessage(arg.data);
                  });
              }
          },
      
          "SendRawMessage": function (message) {
              window.HybridWebView.__SendMessageInternal('RawMessage', message);
          },
      
          "__SendMessageInternal": function (type, message) {
      
              const messageToSend = type + '|' + message;
      
              if (window.chrome && window.chrome.webview) {
                  // Windows WebView2
                  window.chrome.webview.postMessage(messageToSend);
              }
              else if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.webwindowinterop) {
                  // iOS and MacCatalyst WKWebView
                  window.webkit.messageHandlers.webwindowinterop.postMessage(messageToSend);
              }
              else {
                  // Android WebView
                  hybridWebViewHost.sendMessage(messageToSend);
              }
          },
      
          "InvokeMethod": function (taskId, methodName, args) {
              if (methodName[Symbol.toStringTag] === 'AsyncFunction') {
                  // For async methods, we need to call the method and then trigger the callback when it's done
                  const asyncPromise = methodName(...args);
                  asyncPromise
                      .then(asyncResult => {
                          window.HybridWebView.__TriggerAsyncCallback(taskId, asyncResult);
                      })
                      .catch(error => console.error(error));
              } else {
                  // For sync methods, we can call the method and trigger the callback immediately
                  const syncResult = methodName(...args);
                  window.HybridWebView.__TriggerAsyncCallback(taskId, syncResult);
              }
          },
      
          "__TriggerAsyncCallback": function (taskId, result) {
              // Make sure the result is a string
              if (result && typeof (result) !== 'string') {
                  result = JSON.stringify(result);
              }
      
              window.HybridWebView.__SendMessageInternal('InvokeMethodCompleted', taskId + '|' + result);
          }
      }
      
      window.HybridWebView.Init();
      

    그런 다음 프로젝트에 추가 웹 콘텐츠를 추가합니다.

    Warning

    경우에 따라 Visual Studio는 프로젝트의 .csproj 파일에 잘못된 항목을 추가할 수 있습니다. 원시 자산에 기본 위치를 사용하는 경우 .csproj 파일에 이러한 파일 또는 폴더에 대한 항목이 없어야 합니다.

  3. 앱에 HybridWebView 컨트롤을 추가합니다.

    <Grid RowDefinitions="Auto,*"
          ColumnDefinitions="*">
        <Button Text="Send message to JavaScript"
                Clicked="OnSendMessageButtonClicked" />
        <HybridWebView x:Name="hybridWebView"
                       RawMessageReceived="OnHybridWebViewRawMessageReceived"
                       Grid.Row="1" />
    </Grid>
    
  4. API를 HybridWebView 사용하여 JavaScript와 C# 코드 간에 메시지를 보냅니다.

    private void OnSendMessageButtonClicked(object sender, EventArgs e)
    {
        hybridWebView.SendRawMessage($"Hello from C#!");
    }
    
    private async void OnHybridWebViewRawMessageReceived(object sender, HybridWebViewRawMessageReceivedEventArgs e)
    {
        await DisplayAlert("Raw Message Received", e.Message, "OK");
    }
    

    위의 메시지는 추가 처리가 수행되지 않으므로 원시로 분류됩니다. 메시지 내의 데이터를 인코딩하여 고급 메시징을 수행할 수도 있습니다.

C에서 JavaScript 호출#

앱의 C# 코드는 선택적 매개 변수 및 선택적 반환 값을 사용하여 내부 JavaScript 메서드 HybridWebView를 동기 및 비동기적으로 호출할 수 있습니다. 이 작업은 다음과 EvaluateJavaScriptAsync 같은 InvokeJavaScriptAsync 방법으로 수행할 수 있습니다.

  • 메서드는 EvaluateJavaScriptAsync 매개 변수를 통해 제공된 JavaScript 코드를 실행하고 결과를 문자열로 반환합니다.
  • 메서드는 InvokeJavaScriptAsync 매개 변수 값을 선택적으로 전달하는 지정된 JavaScript 메서드를 호출하고 반환 값의 형식을 나타내는 제네릭 인수를 지정합니다. 호출된 JavaScript 메서드의 반환 값을 포함하는 제네릭 인수 형식의 개체를 반환합니다. 내부적으로 매개 변수 및 반환 값은 JSON 인코딩됩니다.

동기 JavaScript 호출

동기 JavaScript 메서드는 및 InvokeJavaScriptAsync 메서드를 사용하여 EvaluateJavaScriptAsync 호출할 수 있습니다. 다음 예제 InvokeJavaScriptAsync 에서는 이 메서드를 사용하여 앱의 웹 콘텐츠에 포함된 JavaScript를 호출하는 방법을 보여 줍니다. 예를 들어 두 숫자를 추가하는 간단한 Javascript 메서드를 웹 콘텐츠에 정의할 수 있습니다.

function AddNumbers(a, b) {
    return a + b;
}

메서드를 AddNumbers 사용하여 C#에서 JavaScript 메서드를 InvokeJavaScriptAsync 호출할 수 있습니다.

double x = 123d;
double y = 321d;

double result = await hybridWebView.InvokeJavaScriptAsync<double>(
    "AddNumbers", // JavaScript method name
    HybridSampleJSContext.Default.Double, // JSON serialization info for return type
    [x, y], // Parameter values
    [HybridSampleJSContext.Default.Double, HybridSampleJSContext.Default.Double]); // JSON serialization info for each parameter

메서드 호출을 사용하려면 작업에 사용되는 형식에 대한 serialization 정보를 포함하는 개체를 지정 JsonTypeInfo 해야 합니다. 이러한 개체는 프로젝트에 다음 partial 클래스를 포함하여 자동으로 생성됩니다.

[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(double))]
internal partial class HybridSampleJsContext : JsonSerializerContext
{
    // This type's attributes specify JSON serialization info to preserve type structure
    // for trimmed builds.
}

Important

코드 생성이 HybridSampleJsContext 프로젝트를 컴파일할 partial 때 구현을 제공할 수 있도록 클래스여야 합니다. 형식이 다른 형식에 중첩된 경우 해당 형식도 이어야 partial합니다.

비동기 JavaScript 호출

비동기 JavaScript 메서드는 및 InvokeJavaScriptAsync 메서드를 사용하여 EvaluateJavaScriptAsync 호출할 수 있습니다. 다음 예제 InvokeJavaScriptAsync 에서는 이 메서드를 사용하여 앱의 웹 콘텐츠에 포함된 JavaScript를 호출하는 방법을 보여 줍니다. 예를 들어 데이터를 비동기적으로 검색하는 Javascript 메서드는 웹 콘텐츠에 정의될 수 있습니다.

async function EvaluateMeWithParamsAndAsyncReturn(s1, s2) {
    const response = await fetch("/asyncdata.txt");
    if (!response.ok) {
            throw new Error(`HTTP error: ${response.status}`);
    }
    var jsonData = await response.json();
    jsonData[s1] = s2;

    return jsonData;
}

메서드를 EvaluateMeWithParamsAndAsyncReturn 사용하여 C#에서 JavaScript 메서드를 InvokeJavaScriptAsync 호출할 수 있습니다.

Dictionary<string, string> asyncResult = await hybridWebView.InvokeJavaScriptAsync<Dictionary<string, string>>(
    "EvaluateMeWithParamsAndAsyncReturn", // JavaScript method name
    HybridSampleJSContext.Default.DictionaryStringString, // JSON serialization info for return type
    ["new_key", "new_value"], // Parameter values
    [HybridSampleJSContext.Default.String, HybridSampleJSContext.Default.String]); // JSON serialization info for each parameter

이 예제에서는 asyncResult Dictionary<string, string> 웹 요청의 JSON 데이터를 포함하는 것입니다.

메서드 호출을 사용하려면 작업에 사용되는 형식에 대한 serialization 정보를 포함하는 개체를 지정 JsonTypeInfo 해야 합니다. 이러한 개체는 프로젝트에 다음 partial 클래스를 포함하여 자동으로 생성됩니다.

[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(Dictionary<string, string>))]
[JsonSerializable(typeof(string))]
internal partial class HybridSampleJSContext : JsonSerializerContext
{
    // This type's attributes specify JSON serialization info to preserve type structure
    // for trimmed builds.  
}

Important

코드 생성이 HybridSampleJsContext 프로젝트를 컴파일할 partial 때 구현을 제공할 수 있도록 클래스여야 합니다. 형식이 다른 형식에 중첩된 경우 해당 형식도 이어야 partial합니다.