Статьи

Использование JavaScript-фреймворков из вашего приложения C # / UWP

У JavaScript, без сомнения, самая  яркая экосистема  . Каждый месяц выпускается несколько миллиардов новых фреймворков ( https://www.javascripting.com/ ).

образ

Как разработчик C #, даже с большим и активным сообществом C #, вы можете иногда чувствовать себя немного ревнивым.

Что если бы мы могли перенести язык JavaScript и экосистему также в мир C #? Что если разработчик C # может использовать JavaScript внутри C #?

Не волнуйтесь! Я очень рад объявить о новом проекте WinRT, который я создал, ChakraBridge,  который позволит вам получить приглашение на вечеринку, как и любой другой веб-разработчик.

образ

Действительно, благодаря Chakra (движок JavaScript, используемый Microsoft Edge) теперь можно разместить один из самых  быстрых движков JavaScript  (а также тот, который  поддерживает ECMAScript 6 ) в любом приложении Universal Windows Platform. ChakraBridge  встраивает движок Chakra в приложение WinRT и предоставляет все необходимые инструменты высокого уровня, чтобы беспрепятственно использовать его в приложении C # / UWP.

Людям, разрабатывающим UWP-приложение на основе HTML / JS / CSS (WWA или размещенное приложение в старом мире)  , не нужно  отдельно размещать Chakra, поскольку он уже является частью песочницы.

Как это использовать?

Это довольно просто: просто зайдите на  https://github.com/deltakosh/JsBridge  и клонируйте проект на жесткий диск.

Теперь у вас есть два варианта: вы можете либо добавить   проект ChakraBridge (который является   библиотекой WinRT ) в ваше решение, либо вы можете сослаться на  ChakraBridge .winmd  из папки / dist.

Инициализация чакры

После ссылки вы можете вызвать эти строки кода, чтобы получить готовую к использованию Чакру:

host = new ChakraHost();

Переменная с именем  host  — ваш контекст JavaScript.

Вы также можете иметь возможность отслеживать сообщения, отправленные на консоль JavaScript. Для этого, пожалуйста, добавьте этот код:

Console.OnLog += Console_OnLog;

После подключения этот обработчик событий будет вызываться каждый раз, когда код JavaScript выполняет « console.log ()».

Какую платформу JavaScript я могу использовать?

Прежде чем определить, что вы можете сделать, вы должны понять, что Chakra — это движок JavaScript, что означает, что вы можете выполнять код JavaScript в своем приложении, но нет ничего, связанного с HTML или CSS.

Затем вы можете выбрать любой фреймворк, не связанный с HTML (операции DOM) или CSS. Вот несколько примеров (но есть еще много ):

После того, как вы выбрали фреймворк, который хотите использовать, вы должны внедрить его в контекст чакры. В моем случае я хотел использовать CDC ( CloudDataConnector ), потому что мне нужен был способ бесшовного подключения к различным поставщикам облачных данных (Amazon, Azure, CouchDB и т. Д.). Вы можете загружать файлы .js и вставлять их в свой проект или загружать их каждый раз при запуске приложения:

await ReadAndExecute("cdc.js");
await ReadAndExecute("azuremobileservices.js");
await ReadAndExecute("cdc-azuremobileservices.js");
await ReadAndExecute("sample.js");

Вы можете заменить  ReadAndExecute  на  DownloadAndExecute,  если предпочитаете ссылаться на живые файлы .js.

Теперь ваш JavaScript-контекст скомпилировал и выполнил указанные файлы.

Обратите внимание, что « sample.js » — это пользовательский файл JavaScript, который содержит код клиента для моего приложения:

var CDCAzureMobileService = new CloudDataConnector.AzureDataService();

var CDCService = new CloudDataConnector.DataService(new CloudDataConnector.OfflineService(), new CloudDataConnector.ConnectivityService());
CDCAzureMobileService.addSource('https://angularpeoplev2.azure-mobile.net/', 'xxxxxxx', ['people']);

CDCService.addSource(CDCAzureMobileService);

var dataContext = {};

var onUpdateDataContext = function (data) {
    if (data && data.length) {
        syncPeople(data);
    }
}

var syncPeople = function (data) {
    sendToHost(JSON.stringify(data), "People[]");
}

CDCService.connect(function (results) {
    if (results === false) {
        console.log("CDCService must first be successfully initialized");
    } else {
        console.log("CDCService is good to go!");
    }
}, dataContext, onUpdateDataContext, 3);

Ничего особенного, я просто использую CDC для подключения к мобильной службе Azure, чтобы получить список людей.

Возвращение данных из мира JavaScript

Затем я верну свои данные из контекста JavaScript. Как вы, возможно, видели в файле « sample.js », когда контекст данных обновляется, я вызываю глобальную функцию  sendToHost.  Эта функция предоставляется  ChakraBridge,  чтобы позволить вам общаться с хостом C #.

Чтобы это работало, вы должны определить, какие типы можно отправлять из JavaScript:

CommunicationManager.RegisterType(typeof(People[]));

Так что теперь, когда  sendToHost  вызывается из контекста JavaScript, на стороне C # будет вызвано определенное событие:

CommunicationManager.OnObjectReceived = (data) =>
{
    var peopleList = (People[])data;
    peopleCollection = new ObservableCollection<People>(peopleList);

    peopleCollection.CollectionChanged += PeopleCollection_CollectionChanged;

    GridView.ItemsSource = peopleCollection;
    WaitGrid.Visibility = Visibility.Collapsed;
};

Очевидно, что вы несете ответственность за сопоставление между вашим объектом JavaScript и вашим типом C # (те же имена свойств).

Вызов функций JavaScript

С другой стороны, вы можете вызывать определенные функции в вашем контексте JavaScript из вашего кода C #. Подумайте, например, о совершении транзакции или добавлении нового объекта.

Итак, сначала давайте создадим функцию для конкретной задачи в нашем файле « sample.js »:

commitFunction = function () {
    CDCService.commit(function () {
        console.log('Commit successful');
    }, function (e) {
        console.log('Error during commit');
    });
}

Чтобы вызвать эту функцию из C #, вы можете использовать этот код:

host.CallFunction("commitFunction");

Если ваша функция принимает параметры, вы также можете передать их:

host.CallFunction("deleteFunction", people.Id);

Текущая версия  ChakraBridge  поддерживает  типы intdoublebool  и  string  .

Отладка в контексте JavaScript

Благодаря Visual Studio все еще можно отлаживать код JavaScript, даже если вы сейчас находитесь в приложении C #. Для этого сначала необходимо включить отладку скрипта в свойствах проекта:

образ

Затем вы можете установить точку останова в своем коде JavaScript.

Но есть одна хитрость, которую нужно знать : вы не можете установить эту точку останова в файлах вашего проекта, поскольку они находятся здесь просто как источник. Вы должны получить доступ к исполняемому коду через часть «Документы сценария» обозревателя решений при работе в режиме отладки:

образ

Как это работает?

Interop

Давайте теперь обсудим, как все работает под капотом.

По сути, Chakra основана на  библиотеке Win32,  расположенной по адресу «C: \ Windows \ System32 \ Chakra.dll» на всех настольных устройствах Windows 10.

Итак, идея здесь заключается в том, чтобы предоставить внутренний класс C #, который будет встраивать все точки входа в DLL через   атрибуты DllImport :

 internal static class Native
    {
        [DllImport("Chakra.dll")]
        internal static extern JavaScriptErrorCode JsCreateRuntime(JavaScriptRuntimeAttributes attributes, 
            JavaScriptThreadServiceCallback threadService, out JavaScriptRuntime runtime);

        [DllImport("Chakra.dll")]
        internal static extern JavaScriptErrorCode JsCollectGarbage(JavaScriptRuntime handle);

        [DllImport("Chakra.dll")]
        internal static extern JavaScriptErrorCode JsDisposeRuntime(JavaScriptRuntime handle);

Список доступных функций довольно длинный. ChakraBridge  предназначен для инкапсуляции этих функций и обеспечения абстракции более высокого уровня.

Другой вариант, который можно рассмотреть здесь: вы также можете использовать замечательную оболочку Роба Павезы, которая называется  js-rt winrthttps://github.com/robpaveza/jsrt-winrt . Это более высокий уровень, чем чистый двигатель Chakra, и он не требует P / Invoke.

Предоставление недостающих частей

Важно понимать, что Chakra предоставляет только движок JavaScript. Но вы, как хост, должны предоставить инструменты, используемые наряду с JavaScript. Эти инструменты обычно предоставляются браузерами ( подумайте о C # без .NET ).

Например,   объект XmlHttpRequest или   функция setTimeout не являются частью языка JavaScript. Они являются инструментами , используемыми на  языке JavaScript в контексте вашего браузера.

Чтобы позволить вам использовать JavaScript-фреймворки,  ChakraBridge  предоставляет некоторые из этих инструментов.

Это постоянный процесс, и  в будущем я или сообщество добавлю больше инструментов в  ChakraBridge.

Давайте теперь посмотрим на реализацию  XmlHttpRequest :

using System;
using System.Collections.Generic;
using System.Net.Http;

namespace ChakraBridge
{
    public delegate void XHREventHandler();

    public sealed class XMLHttpRequest
    {
        readonly Dictionary<string, string> headers = new Dictionary<string, string>();
        Uri uri;
        string httpMethod;
        private int _readyState;

        public int readyState
        {
            get { return _readyState; }
            private set
            {
                _readyState = value;

                try
                {
                    onreadystatechange?.Invoke();
                }
                catch
                {
                }
            }
        }

        public string response => responseText;

        public string responseText
        {
            get; private set;
        }

        public string responseType
        {
            get; private set;
        }

        public bool withCredentials { get; set; }

        public XHREventHandler onreadystatechange { get; set; }

        public void setRequestHeader(string key, string value)
        {
            headers[key] = value;
        }

        public string getResponseHeader(string key)
        {
            if (headers.ContainsKey(key))
            {
                return headers[key];
            }

            return null;
        }

        public void open(string method, string url)
        {
            httpMethod = method;
            uri = new Uri(url);

            readyState = 1;
        }

        public void send(string data)
        {
            SendAsync(data);
        }

        async void SendAsync(string data)
        {
            using (var httpClient = new HttpClient())
            {
                foreach (var header in headers)
                {
                    if (header.Key.StartsWith("Content"))
                    {
                        continue;
                    }
                    httpClient.DefaultRequestHeaders.Add(header.Key, header.Value);
                }

                readyState = 2;

                HttpResponseMessage responseMessage = null;

                switch (httpMethod)
                {
                    case "DELETE":
                        responseMessage = await httpClient.DeleteAsync(uri);
                        break;
                    case "PATCH":
                    case "POST":
                        responseMessage = await httpClient.PostAsync(uri, new StringContent(data));
                        break;
                    case "GET":
                        responseMessage = await httpClient.GetAsync(uri);
                        break;
                }

                if (responseMessage != null)
                {
                    using (responseMessage)
                    {
                        using (var content = responseMessage.Content)
                        {
                            responseType = "text";
                            responseText = await content.ReadAsStringAsync();
                            readyState = 4;
                        }
                    }
                }
            }
        }
    }
}

 

Как видите,   класс XmlHttpRequest использует внутренне  HttpClient  и использует его для имитации   объекта XmlHttpRequest, который вы можете найти в браузере или в файле node.js.

Этот класс затем проецируется (буквально) в контекст JavaScript:

Native.JsProjectWinRTNamespace("ChakraBridge");

На самом деле, все пространство имен проецируется, так как невозможно спроецировать только один класс. Затем выполняется JavaScript для перемещения объекта XmlHttpRequest в глобальный объект:

RunScript("XMLHttpRequest = ChakraBridge.XMLHttpRequest;");

Обработка мусора

Одна из ловушек, с которой вы можете столкнуться, если решите расширить  ChakraBridge,  — это сбор мусора. Действительно, сборщик мусора JavaScript не имеет представления о том, что происходит вне его собственного контекста.

Например, давайте посмотрим, как   разрабатывается функция setTimeout :

internal static class SetTimeout
    {
        public static JavaScriptValue SetTimeoutJavaScriptNativeFunction(JavaScriptValue callee, bool isConstructCall,                                           [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] JavaScriptValue[] arguments,                                           ushort argumentCount, IntPtr callbackData)
        {
            // setTimeout signature is (callback, after)
            JavaScriptValue callbackValue = arguments[1];

            JavaScriptValue afterValue = arguments[2].ConvertToNumber();
            var after = Math.Max(afterValue.ToDouble(), 1);

            uint refCount;
            Native.JsAddRef(callbackValue, out refCount);
            Native.JsAddRef(callee, out refCount);

            ExecuteAsync((int)after, callbackValue, callee);

            return JavaScriptValue.True;
        }

        static async void ExecuteAsync(int delay, JavaScriptValue callbackValue, JavaScriptValue callee)
        {
            await Task.Delay(delay);
            callbackValue.CallFunction(callee);
            uint refCount;
            Native.JsRelease(callbackValue, out refCount);
            Native.JsRelease(callee, out refCount);
        }
    }

SetTimeoutJavaScriptNativeFunction  — это метод, который будет проецироваться в контексте JavaScript. Вы можете заметить, что каждый параметр собирается как JavaScriptValue и приводится к ожидаемому значению. Для функции обратного вызова ( callbackValue ) мы должны указать сборщику мусора JavaScript, что мы храним ссылку, чтобы она не могла освободить эту переменную, даже если никто не удерживает ее в контексте JavaScript:

Native.JsAddRef(callbackValue, out refCount);

Ссылка должна быть освобождена после вызова обратного вызова:

Native.JsRelease(callbackValue, out refCount);

С другой стороны, сборщик мусора в C # не имеет представления о том, что происходит внутри черного ящика чакры. Итак, вы должны позаботиться о сохранении ссылки на объекты или функции, которые вы проецируете в контекст JavaScript. В конкретном случае реализации setTimeout сначала необходимо создать статическое поле, которое указывает на ваш метод C #, чтобы просто сохранить ссылку на него.

Почему бы не использовать веб-просмотр?

Это правильный вопрос, который вы можете задать. Использование только чакры дает большие преимущества:

  • Занимаемая память:  нет необходимости встраивать движки HTML и CSS, так как у нас уже есть XAML.
  • Производительность:  мы можем напрямую контролировать контекст JavaScript и, например, вызывать функцию JavaScript без необходимости проходить через сложный процесс, такой как веб-просмотр.
  • Простота:  веб-просмотр должен перейти на страницу для выполнения JavaScript. Не существует простого способа просто выполнить код JavaScript.
  • Контроль.  Предоставляя собственные инструменты (такие как  XHR  или  setTimeout ), мы имеем высокий уровень детализации для контроля того, что может делать JavaScript.

Идти дальше

Благодаря движку Chakra это начало отличного сотрудничества между C #, XAML и JavaScript. В зависимости от реакции сообщества я планирую добавить больше функций в   проект ChakraBridge, чтобы иметь возможность обрабатывать больше фреймворков JavaScript (например, было бы здорово добавить поддержку рисования на холсте, чтобы иметь возможность использовать все доступные классные фреймворки для построения диаграмм). для JavaScript).

Если вы хотите узнать больше о самой чакре, вы можете зайти в официальный репозиторий образцов чакр:  https://github.com/Microsoft/Chakra-Samples .

Вы также можете найти эти ссылки интересными: