Мост из мира .NET в мир JavaScript

Хочу поделиться интересной находкой — Bridge.net. Если в двух словах, это фреймворк, позволяющий транслировать C# код в JavaScript. Идея, которая движет командой разработчиков очень проста и понятна — транслировать логику настолько идентично, насколько это возможно. Что радует, если вспомнить все различия и специфику этих языков.



Пожалуй, каждый сможет найти свое персональное применение, но я хотел бы отметить те моменты, которые привлекли лично меня:

  • Возможность применения для мобильной разработки. JavaScript работает на всех платформах
  • Применимость для Web. Особенно, если нужно переписать устаревший проект, например, в виде SPA приложения
  • Возможность переиспользовать C# код одновременно на стороне сервера и клиента
  • Трансляция, выполняемая во время сборки проекта
  • Проект находится в стадии активной разработки, есть адекватная поддержка со стороны разработчиков
  • Bridge.net — это OpenSource проект с лицензией на использование в коммерческих целях (лицензия Apache 2.0)

В качестве демонстрации использования Bridge, я хочу поделиться решением задачи валидации полей в Web приложении. Надеюсь, пример будет наглядным, ведь подобная задача может встречаться достаточно часто. Для простоты буду использовать ASP.NET Web Forms.

Итак, у нас есть поле, в которое пользователь должен ввести регистрационный номер автомобиля и нажать на кнопку. В нашем случае кнопка будет проверять корректность ввода (в реальной жизни мы могли бы возвращать, например, зарегистрированные штрафы).

Серверная логика проверки ввода
protected void submitButton_OnClick(object sender, EventArgs e)
{
    var isCorrect = IsCorrectPlateNumber(plateTextBox.Text);
    plateTextBox.BackColor = isCorrect ? Color.LightGreen : Color.Coral;
}
private static bool IsCorrectPlateNumber(string plateNumber)
{
    var success = false;
    if (!string.IsNullOrWhiteSpace(plateNumber))
    {
        plateNumber = plateNumber.Trim();
        if (plateNumber.Length >= 8 || plateNumber.Length <= 9)
        {
            var rgx = new Regex(@"^[АВЕКМНОРСТУХавекмнорстух]\d{3}[АВЕКМНОРСТУХавекмнорстух]{2}\d{2,3}$");
            success = rgx.IsMatch(plateNumber);
        }
    }
    return success;
}




Хорошо, теперь мы хотим снизить нагрузку на сервер и выполнять валидацию поля на стороне клиента. Вариантов тут несколько:

1) Заново написать клиентскую валидацию, которая будет проверять самые простые сценарии, так сказать, «защита от дурака». Основные проверки будут по-прежнему выполняться на стороне сервера.

Простая клиентская валидация
Рег. номер автомобиля (формат X 000 XX 000):<br/>
<asp:TextBox ID="plateTextBox" runat="server"/>
<asp:Button ID="submitButton" runat="server" Text="Проверить" OnClientClick="return validatePlate();" OnClick="submitButton_OnClick"/>

function validatePlate() {
    var value = document.getElementById('<%=plateTextBox.ClientID%>').value;
    if (value && value.length) {
        return true;
    }
    alert("Введите рег. номер автомобиля!");
    return false;
}


2) Портировать серверную валидацию, насколько это возможно, тем самым повторив код. Плюсы очевидны — минимальное расхождение результатов валидации на сервере и на клиенте. Минусы тоже есть — повторяя серверную логику, хотелось бы также поддерживать ее в актульном состоянии, что может добавить головной боли. Переписывать вручную код не буду, он вполне понятен. Давайте лучше посмотрим, как нам помог бы Bridge. Для этого воспользуемся online редактором. Транслированный код будет выглядеть следующим образом:

Полная клиентская валидация
(function (globals) {
    "use strict";

    Bridge.define('Demo.ServerLogic', {
        statics: {
            isCorrectPlateNumber: function (plateNumber) {
                var success = false;
                if (!Bridge.String.isNullOrWhiteSpace(plateNumber)) {
                    plateNumber = plateNumber.trim();
                    if (plateNumber.length >= 8 || plateNumber.length <= 9) {
                        var rgx = new Bridge.Text.RegularExpressions.Regex("constructor", "^[АВЕКМНОРСТУХавекмнорстух]\\d{3}[АВЕКМНОРСТУХавекмнорстух]{2}\\d{2,3}$");
                        success = rgx.isMatch(plateNumber);
                    }
                }
                return success;
            }
        }
    });

    Bridge.init();
})(this);

Нам остается подключить файл Bridge.js (для простоты я взял файл скрипта из онлайн редактора), а также немного модифицировать нашу первоначальную функцию валидации:

function validatePlate() {
    var value = document.getElementById('<%=plateTextBox.ClientID%>').value;
    if (Demo.ServerLogic.isCorrectPlateNumber(value)) {
        return true;
    }
    alert("Неверный рег. номер автомобиля!");
    return false;
}


3) Переиспользовать логику. Если мы хотим поддерживать ее в актуальном состоянии, то можно настроить, чтобы трансляция валидации из C# в JavaScript выполнялась при каждой сборке проекта. Таким образом, логика по-настоящему становится переиспользуемой, программисты не тратят свое время попусту, приложение становится стабильнее, а пользователи еще счастливее! Для этого нужно выполнить несколько шагов:

Встраивание Bridge в проект
1. Создадим в имеющемся солюшене проект WebApplication1.Bridge

2. Установим в него Nuget пакет. Устанавливайте этот пакет в отдельный проект, т.к. при установке удаляются конфликтующие References.



3. В проекте WebApplication1 вынесем логику в отдельный файл/класс. Соответственно, теперь этот класс будет использован в обработчике события нажатия на кнопку (изменения понятны, код не привожу).

ServerLogic.cs
using System.Text.RegularExpressions;

namespace WebApplication1
{
    public class ServerLogic
    {
        public static bool IsCorrectPlateNumber(string plateNumber)
        {
            var success = false;
            if (!string.IsNullOrWhiteSpace(plateNumber))
            {
                plateNumber = plateNumber.Trim();
                if (plateNumber.Length >= 8 || plateNumber.Length <= 9)
                {
                    var rgx = new Regex(@"^[АВЕКМНОРСТУХавекмнорстух]\d{3}[АВЕКМНОРСТУХавекмнорстух]{2}\d{2,3}$");
                    success = rgx.IsMatch(plateNumber);
                }
            }
            return success;
        }
    }
}


4. Добавим созданный файл в проект WebApplication1.Bridge как ссылку

5. Изменим output директорию для проекта WebApplication1.Bridge. Для этого настроим в файле bridge.json:

"output": "../WebApplication1/Scripts/Bridge"

6. После сборки в Solution Explorer нажмем Show All Files и сможем увидеть сгенерированный скрипт webApplication1.js

7. Добавим нужные ссылки и обновим скрипты на странице Demo.aspx:

Demo.aspx скрипты
<script src="Scripts/Bridge/bridge.js" type="text/javascript"></script>
<script src="Scripts/Bridge/webApplication1.js" type="text/javascript"></script>
<script type="text/javascript">
function validatePlate() {
    var value = document.getElementById('<%=plateTextBox.ClientID%>').value;
    if (WebApplication1.ServerLogic.isCorrectPlateNumber(value)) {
        return true;
    }
    alert("Неверный рег. номер автомобиля!");
    return false;
}
</script>


8. Всё. Можно собирать проект и запускать. Теперь любые изменения в логику валидации будут автоматически работать как для серверной проверки, так и для клиентской.

Так, на простом примере, мы увидели как можно легко переиспользовать C# код, даже когда это нужно сделать с помощью JavaScript. И что может представлять бОльшую ценность — как такой подход встраивается в процесс разработки и сборки Web приложения. Однако, это не единственное преимущество Bridge.net. Я вижу его неоспоримым помощником в кроссплатформенной мобильной разработке, особенно для тех, кто не представляет свою жизнь без .NET'а, но об этом в следующий раз!

Комментарии 9

    +2
    Как-то раз участвовал в проекте, где надо было разобрать и задокументировать сложное Web приложение (онлайн-казино) написаное на Bridge.NET. Одной из целей проекта было покапаться и выдать фидбэк начальнику и коллегам об адекватности изучения и использования bridge.net в следующих проектах. Вывод был отрицательный — не понравилось что слишком много тащит кода в JS. Куча System библиотек, переведенных на JS, просто вставляется в один файл с логикой приложения. Полностью от разработки на JS избавиться сложно и получается этакий Франкенштейн с гигантским объемом сиситемного кода на JS.
    Честно говоря, для нового проекта можно уже смотреть на Blazor — решение такой-же проблемы, но системные dll нет необходимости конвертироввать в JS.
      0
      Blazor — решение такой-же проблемы, но системные dll нет необходимости конвертироввать в JS.

      При этом тащится еще и рантайм.
        –1
        Согласен, это один из основных недостатков Bridge.NET. При этом, трудно представить как можно реализовать функции .NET в JS по-другому. Возможно единственное решение проблемы — это разбивка bridge.js на модули. Это позволило бы подключать только те системные классы/библиотеки, которые по факту используются в проекте. Данная функциональность уже давно обсуждается на GitHub.
        0
        На мой взгляд, пропустили вариант — это сделать асинхронный callback-запрос на сервер для валидации полей на сервере. Подобно тому, как это делают формы регистрации с проверкой занят ли указанный логин.

        А так я тоже соглашусь с тем, что сейчас Blazor выглядит очень интересным вариантом при решении таких задач.
          0
          Верно, асинхронные вызовы часто используются для подобных задач. Правда, эта идея не столь показательна, если говорить про снижение нагрузки на сервер, однако, в некоторых задачах без нее никуда (та же проверка занятости логина). Для тех, кому интересно как подобное может быть реализовано с помощью Bridge.NET — в этом примере продемонстрировано выполнение Ajax запроса.

          По поводу Blazor — безусловно, это интересный фреймворк, и как только Microsoft выведет его из Preview, я думаю, он найдет много последователей. В то же время, и Bridge.NET может занять свою нишу, например, в частности применения binding'ов для существующих JS библиотек, здесь можно посмотреть несколько демок: demos.retyped.com

          Проекту Retyped явно есть куда развиваться. Однако уже сейчас для некоторых библиотек предоставлен достаточно годный API. Например, интересно, насколько похожими получились реализация на C# и оригинальный код на JS в данном демо.
            0

            Я для этого изделия городил реализацию React-компонентов на Razor. Получаются вот такие вот монстрики. И даже в райдере нормально работает автокомплит.


            Но в прод это дело не потащил просто потому, что BCL там свой и порезаный (нет и не будет вещей типа WeakReference, например), а потому любые библиотеки под него надо перекомпилировать. Опять же нет аналога IL Linker, так что размер JS-кода выходит неприлично большой, тот же блазор можно ужать до меньших размеров.

              0
              нет и не будет вещей типа WeakReference, например

              А в JS они вообще существуют?

                0

                Ну в том и проблема, что JS в них не умеет. Потому и не будет.

              0

              Fable выглядит гораздо приятнее

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое