Compartir a través de


Vanguardia

Un vistazo a ClearScript

Dino Esposito

Descargar el código de muestra

Dino EspositoHace años, me fascinaba la posibilidad de hospedar todo el motor de VBScript de las páginas Active Server en una aplicación de Visual Basic. Podría crear una increíble prueba de concepto para una empresa que deseara vender contenido editorial en un CD que reutilizaría contenido existente de páginas Active Server fuera de servidores web locales o remotos.

Era a finales de la década de los 90. No existía Microsoft .NET Framework. No existía HTML5. Tan solo unos cuantos de nosotros estábamos explorando activamente las profundidades de Dynamic HTML, pero hospedar el motor de scripting no podría haber sido más sencillo. Tenía que hacer referencia a un control ActiveX, publicar mis objetos ActiveX en el entorno de scripting y listo.

Más recientemente, un cliente me preguntó sobre el método más eficaz para extraer archivos de texto a partir de las consultas de SQL Server. Esa pregunta estaba fuera de mi ámbito de trabajo habitual, así que estuve tentado a responder algo como "Lo siento, lo desconozco". Sin embargo, sabía que este cliente tenía muchos conocimientos de operaciones con bases de datos. Sospeché que había más tela que cortar detrás de la pregunta.

El cliente producía regularmente archivos de texto sin formato (principalmente archivos CSV y XML) a partir de contenidos almacenados en tablas de bases de datos en una instancia de SQL Server. Este hecho molestaba a su personal de bases de datos, puesto que las solicitudes procedían principalmente de empresas con la consiguiente urgencia del mundo de los negocios. No había lógica recurrente que pudiera contribuir a crear rutinas repetibles, por lo menos en un entorno de SQL Server.

Básicamente el cliente estaba buscando una herramienta que el personal de las empresas pudiera programar con lenguajes de script sencillos como VBScript. Los usuarios necesitaban tener acceso controlado a las bases de datos para fines de solo lectura. Huelga decir que la herramienta tenía que ofrecer a los usuarios una posibilidad de crear archivos de texto de forma sencilla. Este hecho me hizo recordar mis días felices con ActiveX y VBScript. Casi me arrepentí al saber que existía una biblioteca relativamente nueva llamada ClearScript (clearscript.codeplex.com).

Integrar ClearScript en Windows Presentation Foundation

ClearScript le permite agregar capacidades de scripting a una aplicación .NET (siempre que emplee .NET Framework 4 o superior). ClearScript es compatible con VBScript, JavaScript y V8. V8 es un motor de JavaScript de código fuente abierto creado por Google e integrado con Chrome. V8 posee un motor de JavaScript de alto rendimiento y encaja bien en escenarios de funcionamiento asíncronos y subprocesamiento múltiple.

El efecto de agregar ClearScript a una aplicación .NET consiste en poder pasar expresiones JavaScript o VBScript al motor y que se procesarán y ejecutarán. Sorprendentemente, no está limitado a utilizar objetos de script sin formato como matrices, objetos JSON y tipos primitivos. Puede integrar bibliotecas externas de JavaScript y objetos .NET administrados por script.

Una vez que haya integrado ClearScript en una aplicación, solo falta informar a la biblioteca de los objetos que puede secuenciar. Esto significa que puede publicar sus propios objetos en el contexto de ClearScript y permitir a los usuarios autorizados cargar y ejecutar scripts existentes o escribir nuevos.

Si desea agregar una capa de personalización y que el usuario agregue piezas de su propia lógica sin incurrir en los costes derivados de las solicitudes de modificación, ClearScripts es necesario pero es posible que no sea suficiente. ClearScript no es más que una pieza del puzzle. Puede que desee ofrecer al usuario un método para administrar sus propios scripts. Asimismo, deberá crear algunos objetos ad hoc que simplifiquen tareas comunes como la creación de archivos.

Esto es lo que hice para ayudar al cliente a generar informes XML y de texto a partir de un grupo de servicios expuestos a través de un back-end de API web. El requisito funcional principal consistía en permitir a los usuarios crear archivos de texto. Para la prueba de concepto, necesité una aplicación de shell para hospedar ClearScript. Elegí una aplicación de Windows Presentation Foundation (WPF) con un cuadro de texto para introducir manualmente el código script. Las iteraciones sucesivas aportaron soporte para una carpeta de entrada predeterminada y a una UI para abrir/importar archivos de script existentes. En la Figura 1 se muestra la aplicación de muestra WPF en funcionamiento.

Una aplicación de muestra de Windows Presentation Foundation que hospeda el motor de ClearScript
Figura 1 Una aplicación de muestra de Windows Presentation Foundation que hospeda el motor de ClearScript

De nuevo, ClearScript es un proyecto de código abierto al que se puede hacer referencia directamente en su proyecto vinculando ensamblados. También puede acceder a paquetes NuGet de terceros, tal y como se muestra en la Figura 2.

Puede instalar ClearScript mediante NuGet
Figura 2 Puede instalar ClearScript mediante NuGet

Inicializar ClearScript

Antes de poder utilizar el motor de scripting mediante programación deberá realizar algunas operaciones. Pero al final, cuando esté totalmente configurado, el código de la Figura 3 es todo lo que necesita para desencadenar la ejecución de código de script.

Figura 3 Código para desencadenar el código de script

public void Confirm()
{
  try
  {
    SonoraConsole.ScriptEngine.Execute(Command);
    OutputText = SonoraConsole.Output.ToString();
  }
  catch(Exception e)
  {
    OutputText = e.Message;
  }
}

El método de confirmación pertenece a la clase de presentador compatible con la vista principal de la aplicación de muestra. Desencadene el método haciendo clic en el botón Ejecutar (visible en la Figura 1). La clase SonoraConsole que aparece en la lista es mi propio contenedor para las clases principales de la biblioteca de ClearScript.

La inicialización del motor de ClearScript se lleva a cabo cuando se inicia la aplicación y se enlaza al evento de iniciación de la clase de la aplicación XAML:

public partial class App : Application
{
  void Application_Startup(Object sender, StartupEventArgs e)
  {
    SonoraConsole.Initialize();
  }
}

La inicialización puede ser tan compleja y sofisticada como desee, pero como mínimo tiene que inicializar el motor de script de su idioma seleccionado. Tiene que hacer disponible la instancia del motor de script a otras partes de la aplicación. Este es un enfoque posible:

public class SonoraConsole
{
   public static void Initialize()
   {
     ScriptEngine = new VBScriptEngine()
   }
   public static ScriptEngine ScriptEngine { get; private set; }
   ...
}

Puede que desee leer en el archivo de configuración qué idioma de scripting debería habilitar en la aplicación. Este es un esquema de configuración posible:

<appSettings>
  <add key="language" value="vb" />
</appSettings>

Cuando tenga lista una instancia de su motor de scripting seleccionado, puede ejecutar cualquier código válido de JavaScript (o VBScript). No puede hacer mucho en un escenario del mundo real hasta que disponga de estas capacidades básicas.

Agregar objetos que permiten ejecutar scripts

Todos los motores de ClearScript exponen una interfaz programable a través de la cual puede agregar objetos que permiten ejecutar scripts en el entorno de tiempo de ejecución. En concreto, utiliza el método AddHostObject, como:

ScriptEngine.AddHostObject("out", new SonoraOutput(settings));
ScriptEngine.AddHostObject("xml", new XmlFacade());

Este método exige dos parámetros. El primer parámetro es el nombre público que los generadores de scripts utilizarán para hacer referencia al objeto publicado. El segundo parámetro es la instancia de objeto. Si mira al fragmento de código anterior, en cualquier JavaScript o VBScript puede utilizar el nombre "out" para invocar a cualquiera de los métodos públicos disponibles en la interfaz SonoraOutput. Este es un ejemplo en JavaScript que hace referencia a lo que se muestra en la Figura 1:

var x = 4;
out.print(x + 1);

Como ya sabrá, una práctica común en JavaScript consiste en nombrar a los miembros en función de la convención camelCase. En programación .NET, la convención PascalCase es más común y también recomendada. En mi implementación de la clase SonoraOutput, opté deliberadamente por seguir la convención JavaScript y llamé al método imprimir en lugar de Imprimir, como sería el caso en la programación sin formato C#.

Basándome en mi experiencia, no necesita saber mucho más para empezar a utilizar ClearScript. La mayoría de las veces, podrá configurar un entorno ClearScript en una aplicación host con el objetivo principal de hacer disponibles objetos específicos de la aplicación. Con mayor frecuencia, se trata de objetos a medida alrededor de objetos de negocio existentes y con una apariencia más atractiva para utilizarlos en un entorno de script.

Los usuarios principales de un entorno ClearScript no suelen ser desarrolladores a tiempo completo. Suelen ser personas con algunas habilidades de desarrollo de software a las que les parece innecesariamente complejo y aburrido tener que conocer todos los detalles de las clases .NET. ClearScript le permite exponer grandes fragmentos de .NET Framework directamente en JavaScript y VBScript. Elegí los objetos a medida diseñados para una mayor simplicidad. A continuación se muestra cómo publicar en ClearScript un tipo en vez de un objeto:

ScriptEngine.AddHostType("dt", typeof(DateTime));

Al hacer referencia a un tipo, le está dando a los usuarios el poder de crear mediante programación instancias de dicho tipo. Por ejemplo, la línea de código anterior aporta el poder del objeto .NET Date Time al entorno de scripting. Ahora, el siguiente código JavaScript es posible:

var date = new dt(1998, 5, 20);
date = date.AddDays(1000);
out.print(date)

Desde dentro del código JavaScript, se está aprovechando del poder completo de métodos como AddDays y AddHours. ¿Qué ocurre si desea obtener la diferencia entre dos fechas? Puede hacer algo así:

var date1 = new dt(1998, 5, 20);
var date2 = date1.AddDays(1000);
var span = date2.Subtract(date1);
out.print(span.Days)

El objeto TimeSpan se controla correctamente y la expresión span.Days sólo devuelve 1000. Esto se debe a la naturaleza dinámica del lenguaje JavaScript, que determina dinámicamente que el objeto denominado "span" exponga un miembro denominado Days (días). Si desea crear una instancia TimeSpan, primero debe informar al motor de que está ahí.

Para evitar exponer un millón de tipos distintos, ClearScript le permite hospedar un ensamblado completo. Este es un enfoque posible:

ScriptEngine.AddHostObject("dotnet",
  new HostTypeCollection("mscorlib", "System.Core"));

La palabra clave DotNet se convierte en la clave para acceder a cualquier tipo y miembro estático en mscorlib y System.Core. Crear un nuevo objeto de fecha lleva algo más de tiempo, pero le permite trabajar explícitamente con objetos 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);

El fragmento de código JavaScript imprime el número 2, que resulta de la suma de dos objetos TimeSpan distintos cada uno de los cuales cuenta por 24 horas. Una de las cosas con las que ClearScript no funciona bien es con la sobrecarga de operadores. Simplemente no existe. Esto significa que para sumar fechas o periodos de tiempo tiene que utilizar métodos como Agregar o Restar. También es compatible con reflexión.

Generar la Salida

La herramienta que ve en la Figura 1 debe ser capaz de mostrar algunos resultados al usuario. De forma predeterminada, el objeto SonoraOutput agregado al motor ClearScript solo mantiene un objeto interno de StringWriter. De hecho, todo el texto procesado por el método de impresión está escrito en el escritor subyacente. El contenido del escritor se expone al mundo exterior por medio de la clase SonoraConsole. Esta clase es el único punto de contacto entre el motor ClearScript y la aplicación host. El presentador de la aplicación host devuelve el contenido del escritor de cadenas por medio de una propiedad. Dicha propiedad se enlaza a continuación a un TextBlock en el WPF UI. El método de impresión escribe en la UI por medio del escritor de cadenas. El método clr borra el búfer y la UI.

Guardar en un archivo de texto

Mi cliente solo necesitaba crear archivos de texto, la mayoría CSV. Esto es relativamente sencillo. Solo tuve que crear un método de archivos y pasar directamente algún contenido de texto. También podía dejarle recuperar cualquier cosa ya imprimida en la pantalla y guardarla en el búfer interno. El aspecto más problemático al trabajar con archivos es su nombre y ubicación. Para que el scripting sea muy rápido, tiene que ser muy fácil crear y recuperar archivos. Conseguí tener dos carpetas predeterminadas: una para entrada y otra para salida. También di por hecho que todos los archivos eran TXT. Si no se especifica ningún nombre de archivo, los archivos adquieren un nombre predeterminado.

Es posible que estos principios fueran muy restrictivos para algunos escenarios pero mi proyecto no era más que una prueba de concepto para que una herramienta produjera archivos, no para que los guardara. Tal y como se muestra en la Figura 4, pude encapsular el objeto XmlWriter en un bonito componente y crear un archivo XML por medio de script.

Crear un archivo XML por medio de Script
Figura 4 Crear un archivo XML por medio de Script

Resumen

¿Cuál es el objetivo de crear un archivo XML por medio de script? De hecho, es lo mismo que tener capacidades de script en algunas aplicaciones de empresa. Necesita script porque desea automatizar tareas. En algunos casos, crear archivos XML o de texto ad hoc es todo lo que necesita. Quizás puede ejecutar consultas en SQL Server e importarlas a CSV, pero para eso necesita acceso de administrador a la base de datos de producción y, lo que es más importante, las habilidades necesarias. Yo mismo tendría problemas para utilizar xp_cmdshell para recuperar archivos de texto a partir de consultas SQL Server. Para un desarrollador, puede que no sea difícil organizar algunos objetos ad hoc fáciles de utilizar que se han creado para scripting.

A mi cliente le encantó esta idea tanto como a mí utilizar ClearScript. Me pidió que agregara muchos más objetos al entorno dinámico. Acabé agregando una capa de inversión de control para configurar objetos para ser cargarlos en el inicio. También está pensando en implementar ClickOnce en toda la empresa cuando aparezcan nuevas herramientas.


Dino Esposito es el coautor de “Microsoft .NET: Architecting Mobile Applications Solutions for the Enterprise” (Microsoft Press, 2014) y del próximo “Programming ASP.NET MVC 5” (Microsoft Press, 2014). Como evangelizador técnico para las plataformas .NET Framework y Android en JetBrains y orador frecuente en eventos mundiales de la industria, Esposito comparte su visión sobre el software en software2cents.wordpress.com y en Twitter en twitter.com/despos.

Gracias a los siguientes expertos técnicos por revisar este artículo: Equipo de Microsoft ClearScript