Много лет назад я был очарован возможностью хостинга всего ядра VBScript страниц Active Server внутри приложения на Visual Basic. Я мог бы создать потрясающий прототип для компании, желающей продавать книги на компакт-дисках, который повторно использовал бы существующий контент страниц Active Server вне локальных веб-серверов.
Это был конец 1990-х. Microsoft .NET Framework тогда еще не было. Как и HTML5. Лишь некоторые из нас активно исследовали глубины Dynamic HTML, тем не менее хостинг скриптового ядра вряд ли мог бы быть проще. Я должен был сослаться на ActiveX-элемент, опубликовать свои ActiveX-объекты в скриптовой среде, и считайте, что все готово.
Совсем недавно один из клиентов спросил у меня о самом эффективном способе извлечения текстовых файлов с помощью запросов к SQL Server. Этот вопрос не попадал в рамки обычно рассматриваемых мной тем, поэтому у меня было искушение ответить нечто вроде «Простите, я не знаю». Но мне было известно, что этот клиент активно занимается эксплуатацией баз данных. И заподозрил, что за этим вопросом скрывается что-то большее.
Этот клиент регулярно создает чисто текстовые файлы (в основном CSV- и XML-файлы) из контента, хранящегося в таблицах базы данных внутри экземпляра SQL Server. Это раздражает его персонал, обслуживающий базу данных, так как запросы по большей части поступают от бизнес-пользователей с обычной для любого бизнеса срочностью. Логики, которая могла бы помочь в создании повторяемых процедур, не было — по крайней мере, в среде SQL Server.
В итоге этот клиент искал какой-нибудь инструмент, с помощью которого бизнес-пользователи могли бы легко программировать свои запросы, применяя такие простые скриптовые языки, как VBScript. Пользователям требовался контролируемый доступ к базам данных только для чтения. Разумеется, такой инструмент должен был бы позволять легко создавать текстовые файлы. Это напомнило мне о счастливых временах ActiveX и VBScript. Меня чуть не захлестнуло нахлынувшими воспоминаниями, но тут я услышал о сравнительно новой библиотеке — ClearScript (clearscript.codeplex.com).
Интеграция ClearScript в Windows Presentation Foundation
ClearScript позволяет добавлять поддержку работы со скриптами в .NET-приложение (если оно использует .NET Framework 4 или выше). ClearScript поддерживает VBScript, JavaScript и V8. V8 — это ядро JavaScript с открытым исходным кодом, созданное Google и интегрированное с Chrome. В V8 ядро JavaScript обладает высокой производительностью и хорошо подходит в условиях многопоточности и асинхронных операций.
В результате добавления ClearScript в .NET-приложение вы можете передавать JavaScript- или VBScript-выражения этому ядру, и они будут обрабатываться и выполняться. Любопытно, что вы не ограничены использованием чисто скриптовых объектов, таких как массивы, JSON-объекты и элементарные типы. Вы можете интегрировать внешние JavaScript-библиотеки и .NET-объекты, управляемые скриптами.
После интеграции ClearScript в приложение нужно лишь сообщить этой библиотеке об объектах, которыми она может манипулировать через скрипты. Это означает, что вы публикуете свои объекты в контексте ClearScript и разрешаете авторизованным пользователям загружать и запускать существующие скрипты или писать новые.
Если вы хотите добавить уровень адаптации и дать возможность пользователю вносить свои части логики, не влекущие издержек запросов на изменение, то ClearScript необходим, но может оказаться недостаточен. ClearScript — лишь один из фрагментов головоломки. Вам может понадобиться какой-то способ сделать так, чтобы пользователь управлял своими скриптами. Кроме того, вы должны создать некоторые специфические объекты, упрощающие распространенные задачи вроде создания файлов.
Вот что я сделал, чтобы помочь одному из клиентов генерировать текстовые и XML-отчеты от набора сервисов, предоставляемых через серверную часть Web API. Основное функциональное требование заключалось в том, чтобы пользователи могли создавать текстовые файлы. Для проверки концепции мне понадобилось приложение-оболочка (shell application) в качестве хоста ClearScript. Я предпочел задействовать WPF-приложение (Windows Presentation Foundation) с текстовым полем для ввода кода скрипта вручную. На последующих итерациях я добавил поддержку папки ввода по умолчанию и UI для открытия/импорта существующих файлов скриптов. На рис. 1 показано это WPF-приложение в действии.
Рис. 1. Пример WPF-приложения — хоста ClearScript
ClearScript является проектом с открытым исходным кодом, на который можно напрямую ссылаться в своем проекте, связав с ним нужные сборки. Кроме того, можно воспользоваться сторонними NuGet-пакетами, как показано на рис. 2.
Рис. 2. Вы можете установить ClearScript через NuGet
Инициализация ClearScript
Прежде чем вы сможете программно использовать скриптовое ядро, вам придется выполнить некоторую подготовительную работу. Но после этого для запуска выполнения скриптового кода вам понадобится лишь код, приведенный на рис. 3.
Рис. 3. Код для запуска скриптового кода
public void Confirm()
{
try
{
SonoraConsole.ScriptEngine.Execute(Command);
OutputText = SonoraConsole.Output.ToString();
}
catch(Exception e)
{
OutputText = e.Message;
}
}
Метод Confirm принадлежит классу презентатора, поддерживающему основное представление приложения-примера. Для запуска этого метода достаточно щелкнуть кнопку Run (видна на рис. 1). Класс SonoraConsole, на который есть ссылка в листинге, — это просто моя оболочка базовых классов библиотеки ClearScript.
Инициализация ядра ClearScript происходит при запуске приложения и его связывании с событием Startup XAML-класса Application:
public partial class App : Application
{
void Application_Startup(Object sender, StartupEventArgs e)
{
SonoraConsole.Initialize();
}
}
Инициализация может быть сколь угодно сложной, но, как минимум, должна происходить инициализация скриптового ядра выбранного вами языка. Вы должны создать экземпляр скриптового ядра, доступный остальным частям приложения. Вот один из возможных подходов:
public class SonoraConsole
{
public static void Initialize()
{
ScriptEngine = new VBScriptEngine()
}
public static ScriptEngine ScriptEngine { get; private set; }
...
}
Можно считывать из конфигурационного файла, какой скриптовый язык следует поддерживать в приложении. Ниже показана одна из возможных конфигурационных схем:
<appSettings>
<add key="language" value="vb" />
</appSettings>
Как только у вас появляется экземпляр выбранного скриптового ядра, вы можете выполнять любой допустимый код на JavaScript (или VBScript).
Добавление объектов с поддержкой скриптов
Все ядра ClearScript предоставляют программируемый интерфейс, через который можно добавлять объекты с поддержкой скриптов (scriptable objects) в среду периода выполнения. В частности, вы используете метод AddHostObject:
ScriptEngine.AddHostObject("out", new SonoraOutput(settings));
ScriptEngine.AddHostObject("xml", new XmlFacade());
Этот метод требует два параметра. Первый — открытое имя, которое скриптеры будут использовать для ссылки на публикуемый объект. Второй — это просто экземпляр объекта. Говоря о предыдущем фрагменте кода, в любом скрипте на JavaScript или VBScript можно использовать имя «out» для запуска любых открытых методов, доступных в интерфейсе SonoraOutput. Вот пример на JavaScript, который ссылается на то, что показано на рис. 1:
var x = 4;
out.print(x + 1);
Как вы, вероятно, знаете, в JavaScript принято именовать члены по соглашению camelCase. В .NET-программировании более распространено и рекомендуется соглашение PascalCase. В своей реализации класса SonoraOutput я умышленно последовал соглашению для JavaScript и назвал метод как print, а не Print, как это делается при программировании на чистом C#.
Как показывает мой опыт, чтобы приступить к работе с ClearScript, вам нужно знать и понимать не так уж и много. По большей части вы можете конфигурировать среду ClearScript в хост-приложении с главной целью сделать доступными объекты, специфичные для приложения. Чаще всего это будут адаптированные объекты, обертывающие существующие бизнес-объекты и более удобные для использования в скриптовой среде..
Основными пользователями среды ClearScript обычно являются вовсе не профессиональные разработчики, а люди с некоторыми навыками в программировании, которые не считают нужным тратить время на изучение всех деталей .NET-классов. ClearScript позволяет предоставлять довольно большие части .NET Framework непосредственно для кодирования на JavaScript и VBScript. Я предпочел разработать адаптированные объекты так, чтобы они были предельно простыми в использовании. Вот как можно опубликовать в ClearScript какой-либо тип вместо объекта:
ScriptEngine.AddHostType("dt", typeof(DateTime));
Добавив ссылку на тип, вы предоставляете пользователям возможность программного создания экземпляров этого типа. Например, в предыдущей строке кода в скриптовую среду вводится мощь .NET-объекта DateTime. И теперь становится возможным написание следующего кода на JavaScript:
var date = new dt(1998, 5, 20);
date = date.AddDays(1000);
out.print(date)
Из JavaScript-кода вы используете преимущества таких методов, как AddDays и AddHours. А если нужно получить разницу между двумя датами? Это можно сделать примерно так:
var date1 = new dt(1998, 5, 20);
var date2 = date1.AddDays(1000);
var span = date2.Subtract(date1);
out.print(span.Days)
Объект TimeSpan обрабатывается корректно, и выражение span.Days просто возвращает 1000. Это связано с динамической природой языка JavaScript, который динамически определяет, что объект span предоставляет член Days. Если вы хотите вместо этого создать экземпляр TimeSpan, то должны сначала сообщить ядру о наличии такого объекта.
Чтобы избежать предоставления миллиона разных типов, ClearScript поддерживает хостинг целой сборки. Ниже показан один из возможных подходов:
ScriptEngine.AddHostObject("dotnet",
new HostTypeCollection("mscorlib", "System.Core"));
Ключевое слово dotnet теперь становится ключом к доступу к любым типам и статическим членам внутри mscorlib и System.Core. Создание нового объекта date занимает чуть больше времени, зато вы можете явным образом работать с объектами TimeSpan:
var date1 = new dotnet.System.DateTime(1998, 5, 20);
var ts1 = new dotnet.System.TimeSpan(24, 0, 0);
var ts2 = ts1.Add(new dotnet.System.TimeSpan(24, 0, 0));
out.print(ts2.Days);
Этот фрагмент JavaScript-кода выводит число 2, полученное из суммы двух разных объектов TimeSpan, каждый из которых отсчитывает 24 часа. Один из недостатков ClearScript — она не поддерживает перегрузку операторов. А значит, чтобы просуммировать даты или интервалы времени, вам придется использовать такие методы, как Add или Subtract. Механизм отражения поддерживается.
Генерация вывода
Инструмент, который вы видите на рис. 1, должен быть способен отображать какие-то результаты пользователю. По умолчанию объект SonoraOutput, добавляемый в ядро ClearScript, просто поддерживает внутренний объект StringWriter. Весь текст, обрабатываемый методом print, на самом деле записывается в нижележащий объект записи (writer). Его содержимое предоставляется внешнему миру через класс SonoraConsole. Этот класс — единственная точка контакта между ядром ClearScript и хост-приложением. Презентатор хост-приложения просто возвращает контент StringWriter через свойство. Затем это свойство связывается с TextBlock в WPF UI. Метод print пишет в UI через StringWriter. Метод clr очищает буфер и UI.
Сохранение в текстовый файл
Моему клиенту нужно было лишь создавать текстовые файлы, в основном CSV-файлы. Добиться этого сравнительно легко. Достаточно было создать файловый метод и напрямую передавать ему какой-то текстовый контент. Я также мог бы разрешить копировать все, что уже имеется на экране и сохранять это во внутренний буфер. Самый проблематичный аспект, с которым приходится иметь дело с файлами, — их именование и расположение. Чтобы скрипт по-настоящему быстро работал, создание и получение файлов должны быть тривиально простыми. Я обошелся двумя папками по умолчанию: одной для ввода и другой для вывода. Кроме того, я исхожу из предположения, что все файлы имеют расширение .txt. Если имя файла не указано, ему присваивается имя по умолчанию.
Эти допущения, возможно, слишком ограничительные в некоторых сценариях, но мой проект был просто проверкой концепции (прототипом) средства, которое создает файлы, но не хранит их. Как демонстрирует рис. 4, я мог бы легко обернуть объект XmlWriter в удобный компонент и создавать XML-файлы через скрипт.
Рис. 4. Создание XML-файла через скрипт
Заключение
Какой смысл создавать XML-файл скриптом? На самом деле это то же самое, что включение скриптовой функциональности в корпоративные приложения. Вам нужен скрипт, потому что вы хотите автоматизировать задачи. В некоторых случаях создание специализированных текстовых или XML-файлов — как раз то, что нужно. Скажем, вы можете выполнять запросы к SQL Server и импортировать их в CSV, но это требует административного уровня доступа к производственной базе данных и, что важнее, соответствующих знаний. У меня самого возникли бы проблемы, используй я xp_cmdshell для получения текстовых файлов из запросов к SQL Server. Для разработчика не составляет труда подготовить какие-то специализированные и простые в использовании объекты, которыми можно манипулировать в скриптах.
Моему клиенту эта идея понравилась не меньше, чем мне — ClearScript. Он попросил меня добавить гораздо больше объектов в динамическую среду. В конечном счете я добавил уровень инверсии управления (inversion of control, IoC) для конфигурирования объектов, загружаемых при запуске приложения. Теперь этот клиент подумывает о применении развертывания ClickOnce по всей компании, как только выпускаются новые средства.