2017. 11. 9. 15:30

자마린에서 웹뷰를 이용한 웹앱을 만들던 중 재미있는 글을 발견했습니다.

 

참고 : Xamarin Forums - What is the easiest way to call c# method from javascript in webview? 의 'Hunuman'님 답변

오홋?

자바스크립트로 임의 URL로 쏴주고 웹브라우저 컨트롤은 페이지 이동을 감지하여 알맞은 함수를 매개변수와 함께 호출해 주면 된다는 것입니다.

 

* 이 글의 예제는 C#과 Xamarin로 되어 있습니다. *

 

 

1. 이 방법 써보기

이 방법의 큰 장점은 페이지 이동 전에 이벤트만 준다면 어떤 SDK건 어떤 플랫폼이건 하나의 함수를 이용할 수 있다는 것입니다.

 

단점은 웹앱이 아닐 때는 별도의 예외 처리를 해야 합니다.

그런데 이건 큰 단점이 아닌 것이 웹앱이 아닐 때는 자바스크립트를 심는 방식도 에러를 내기 때문에 예외 처리를 하긴 해야 합니다.

많이 번잡스럽냐 아니냐 차이죠.

 

또 다른 단점은 URL 쿼리의 한계상 보낼 수 있는 데이터양이 적다는 것입니다.

 

 

1-1. 사용 방법

1) 웹뷰에 'Navigating'이벤트를 추가한다.

2) 'Navigating'이벤트가 발생할 때 URL을 확인한다.

3) 확인한 URL이 내가 지정한 이름이면 비하인드 호출로 취급한다.

4) URL에서 호출할 함수와 매개변수를 분리한다.

5) 비하인드 함수를 호출해 준다.

 

1-1-1. HTML 작성

프로젝트에 html 파일을 생성하고 다음 코드를 넣습니다.

<!DOCTYPE html>
 
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title></title>
    <script>
        function CallFun()
        {
            location.href = "http://CSCommand?fname=hello&a=1&b=ccc";
        }
    </script>
</head>
<body>
    <button onclick="CallFun();" >눌러</button>
</body>
</html>

 

생성한 HTML파일의

속성 > 출력 디렉터리에 복사

속성을 "새 버전이면 복사" 혹은 "항상 복사"로 설정합니다.

 

 

1-1-2. 비하인드 코드 작성

생성한 웹브라우저 컨트롤에 'Navigating'이벤트를 연결합니다.

webBrowser1.Navigating += WebBrowser1_Navigating;
//페이지 이동
webBrowser1.Navigate(Application.StartupPath + @"\HTMLPage1.html");

 

연결한 이벤트에 다음과 같이 URL을 읽어 들여 처리하는 코드를 넣습니다.

아래는 예입니다.

자신이 사용하려는 기능에 맞게 작성합니다.

private void WebBrowser1_Navigating(object sender, WebBrowserNavigatingEventArgs e)
{
    if (e.Url.Host == "CSCommand".ToLower())
    {
        CallFun(e.Url.Query);
        e.Cancel = true;
    }                
}

 

여기서 중요한 코드는

e.Cancel = true;

인데 페이지 이동을 취소하기 위한 코드입니다.

비하인드 함수를 호출하려는 목적인 경우 페이지 이동을 하지 않게 해줍니다.

 

 

저는 URL을 처리하는 함수를 만들었습니다.

이 함수에서는 'fname'로 받은 함수를 호출합니다.

 

URL쿼리를 쉽게 관리하기 위해 누겟에서 'Mono.HttpUtility'를 참조하였습니다.

private void CallFun(string sQuery)
{
    //?a=1&b=ccc
    //using Mono.Web;

    //쿼리 바인딩
    NameValueCollection nvcQuery = HttpUtility.ParseQueryString(sQuery);

    Type thisType = this.GetType();
    //호출할 메소드
    MethodInfo theMethod = thisType.GetMethod(nvcQuery["fname"]);
    //넘길 파라메타
    object[] parametersArray = new object[] { Convert.ToInt32( nvcQuery["a"])
            , nvcQuery["b"] };
    theMethod.Invoke(this, parametersArray);
}

public void hello(int a, string b)
{
    MessageBox.Show(string.Format("성공 : a={0}, b={1}", a, b));
}

 

 

1-2. 테스트해 보기

이제 실행해 보면 아래와 같이 동작합니다.

 

 

성공적으로 'hello'함수를 호출하였습니다.

 

테스트에 사용한 프로젝트입니다.

github - dang-gun/DotNetSamples/WebViewCallJs_Winform/

 

WindowsFormsApp1.zip
다운로드

 

 

2. 알아둘 내용
2-1. 기존

원래 웹뷰와 같은 웹브라우저 컨트롤에서 자바스크립트로 비하인드 함수를 호출하려면 보통 웹뷰에 자바스크립트를 심어서 호출해야 합니다.

대부분의 웹브라우저 SDK가 비슷한 방식을 지원하기 때문에 크게 문제가 되진 않습니다.

 

문제는 다양한 SDK들의 이름이 미묘하게 다르거나(대소문자 같은 거) 기능이 미묘하게 다른 경우가 있습니다.

이 문제를 해결하려면 하나의 모델이나 함수로 처리하기 위한 랩핑 작업을 해줘야 합니다.

 

멀티플랫폼을 지원하는 프래임웍이나 엔진을 사용하더라도 공통으로 사용되는 웹브라우저를 지원하지 않거나 플랫폼별로 기본 지원되는 SDK를 이용한다면 결국 어느 정도 수작업을 해야 한다는 단점이 있습니다.

 

2-2. 응용 예

제가 몇 년 전에 윈도우와 리눅스(와인)에서 같이 사용하는 프로그램을 만든 적이 있습니다.

리눅스에서는 IE  SDK가 없으므로 게코(Gecko) SDK를 사용했습니다.

처음에는 IE SDK가 와인에서도 되는지 알고 개발했다가 완성되고 나서야 안되는 걸 알아서 다시 랩핑 작업을 했습니다.

 

결국 윈도우용은 IE를 썼고 리눅스용은 게코를 사용했습니다.

진짜 미묘하게 양쪽의 기능과 이름이 달라서 하나로 퉁치려는 계획도 실패했습니다.

그래서 각자 랩핑해서 쓰는 식으로 처리했습니다.

그나마 다행인 건 이름은 비슷해서 쉽게 처리했다는 것이.....ㅎㅎㅎ

 

만약 저 방식을 사용했다면 별도의 작업이 거의 필요 없었을 것입니다.

 

또 한 가지는 자마린 폼을 사용하는 경우입니다.

이 경우 자바스크립트에서 비하인드 함수를 호출하려면 안드로이드, IOS, UWP에 각각 자바스크립트 삽입과 호출에 관련된 작업을 해줘야 합니다.

(참고 : Xamarin Guides - Implementing a HybridWebView )

 

그런데 이 방법을 쓰면 각 플랫폼별로 따로 작업할 거 없이 PCL에서 한 번에 처리가 가능합니다.

 

 

3. 자마린으로 응용해 보기

자마린을 이용하여 위 방식을 사용해 봅시다.

 

크로스 플랫폼 앱(Cross Platform App)를 생성하고 안드로이드, IOS, UWP 를 모두 생성합니다.

"MainPage.xaml"에 비하인드 코드에 다음 코드를 넣습니다.

public MainPage()
{
    InitializeComponent();

    WebView webView = new WebView
    {
        Source = new UrlWebViewSource
        {
            Url = "http://www.google.com/",
        },
        VerticalOptions = LayoutOptions.FillAndExpand
    };

    //네비게이션 연결
    webView.Navigating += WebView_Navigating;

    this.Content = new StackLayout
    {
        Children =
            {
                webView
            }
    };
}

private void WebView_Navigating(object sender, WebNavigatingEventArgs e)
{
    Uri aaa = new Uri(e.Url);
    if (aaa.Host == "CSCommand".ToLower())
    {
        //MessageBox.Show("성공 : " + e.Url.Query);
        DisplayAlert("Alert", "성공 : " + aaa.Query, "OK");
        //CallFun(aaa.Query);
        e.Cancel = true;
    }
}

 

로컬에 있는 파일을 읽으려면 플랫폼별로 작업을 해야 하니 웹서버에 '1-1-1. html 작성'에서 만든 파일을 올리고 그 주소로 연결해서 테스트합니다.

 

IOS는 제가 맥이 없어서 빌드가 안 됩니다.

안드로이드와 UWP는 아래와 같이 표시됩니다.

 

 

 

오호~

추가 코드 없이 멀티플랫폼 동작이 되고 있습니다.

 

이번에 사용된 테스트 프로젝트입니다.

WebVieW_CallJs.zip
다운로드

 

 

마무리

물론 여기서 세부적인 기능을 이용하려면 결국 플랫폼별로 작업해야 합니다.

하지만 공통 코드로 처리할 수 있는 것들은 이렇게 처리하는 것이 관리 면에서 좋죠.

 

자마린에서 제공하는 가이드를 보면 자마린 웹뷰도 가이드와 비슷한 방식으로 만들어진 게 아닌가 생각됩니다.

(참고 : Xamarin Guides - Implementing a HybridWebView )

그냥 이 가이드대로 만드는 것이 더 편하지 않을까 하는 생각을 하고 있습니다 ㅎㅎㅎ