Cutting Edge

Um olhar sobre o ClearScript

Dino Esposito

Baixar o código de exemplo

Dino EspositoAlguns anos atrás, eu estava fascinado pela perspectiva de hospedar todo mecanismo VBScript das páginas do Active Server dentro de um aplicativo do Visual Basic. Eu poderia criar uma comprovação de conceito emocionante para uma empresa disposta a vender conteúdo editorial em CD que reutilizaria páginas do Active Server existentes justamente fora dos servidores da Web locais ou remotos.

Era o final dos anos 90. Não havia o Microsoft .NET Framework. Não havia o HTML5. Somente alguns de nós estavam explorando ativamente as essências do Dynamic HTML, mesmo assim hospedar o mecanismo de script não poderia ser sido mais fácil. Tive que fazer referência a um controle ActiveX, publicar meus objetos ActiveX no ambiente de script e eu estava pronto para ir.

Mais recentemente, um cliente me perguntou sobre a maneira mais eficaz de extrair arquivos de texto das consultas do SQL Server. Essa pergunta estava fora da variedade de tópicos que eu tratava, por isso fiquei tentado a responder com algo assim, “Sinto muito, eu não sei”. No entanto, eu sabia que este cliente era forte em operações de banco de dados. Eu suspeitei de que havia mais motivos por trás da pergunta.

O cliente estava regularmente produzindo arquivos de texto sem formatação (principalmente arquivos CSV e XML) fora do conteúdo armazenado nas tabelas de banco de dados dentro de uma instância do SQL Server. Isso incomodava sua equipe de banco de dados, visto que as solicitações vinham na maioria de empresas com a urgência comum dos problemas de negócios. Não havia lógica recorrente que poderia ajudar a criar rotinas repetíveis—pelo menos em um ambiente do SQL Server.

Finalmente, o cliente estava procurando por uma ferramenta que as pessoas pudessem programar usando linguagens de script fáceis como VBScript. O usuário precisavam de acesso controlado aos bancos de dados para fins somente de leitura. Desnecessário dizer, a ferramenta tinha que oferecer aos usuários a chance de criar arquivos de texto facilmente. Isso me lembrou dos dias felizes do ActiveX and VBScript. Eu quase tive uma onda de arrependimento quando ouvi sobre a biblioteca relativamente nova chamada ClearScript (clearscript.codeplex.com).

Integrar o ClearScript no Windows Presentation Foundation

O ClearScript permite que você adicione capacidades de script para um aplicativo do .NET (contanto que ele use o .NET Framework 4 ou superior). O ClearScript oferece suporte para o VBScript, JavaScript e V8. O V8 é um mecanismo JavaScript de código-fonte aberto criado pelo Google e integrado com o Chrome. O V8 tem um mecanismo JavaScript de alto desempenho e se adapta bem dentro dos cenários de operação assíncronos e multithreading.

O resultado de adicionar o ClearScript em um aplicativo .NET é que você pode passar expressões do JavaScript ou VBScript para o mecanismo e elas serão processadas e executadas. Interessantemente, você não está limitado a usar objetos de script sem formatação, como matrizes, objetos JSON e tipos primitivos. Você pode integrar bibliotecas do JavaScript externas e objetos do .NET gerenciados por script.

Quando você tiver integrado o ClearScript em um aplicativo, tudo que resta é permitir que a biblioteca saiba sobre os objetos que ela pode criar scripts. Isso significa que você publica seus próprios objetos no contexto do ClearScript e permite que usuários autorizados carreguem e executem scripts existentes ou escrevam novos.

Se você deseja adicionar um camada de personalização e permitir que o usuário adicione partes de sua própria lógica sem incorrer os custos de solicitações de alteração, o ClearScript é necessário, mas pode não ser suficiente. O ClearScript é apenas uma peça do quebra-cabeça. Você talvez queira fornecer uma maneira para o usuário gerenciar seus próprios scripts. Além disso, você deve criar alguns objetos localmente que simplificam tarefas comuns como criar arquivos.

Isso é o quê fiz para ajudar um cliente a gerar relatórios de XML e de texto para uma variedade de serviços expostos através de um back-end de API da Web. O requisito funcional principal foi permitir que os usuários criassem arquivos de texto. Para a comprovação de conceito, eu precisava de um aplicativo do shell para hospedar o ClearScript. Eu optei por um aplicativo do Presentation Foundation (WPF) com uma caixa de texto para inserir o código de script manualmente. As iterações sucessivas adicionaram suporte a uma pasta de entrada padrão e uma interface de usuário para abrir/importar arquivos de script existentes. A Figura 1 mostra o aplicativo de amostra do WPF em ação.

Um aplicativo de amostra do Windows Presentation Foundation hospedando o mecanismo ClearScript
Figura 1 Um aplicativo de amostra do Windows Presentation Foundation hospedando o mecanismo ClearScript

Novamente, o ClearScript é um projeto de código-fonte aberto que pode ser referenciado diretamente em seu projeto ao vincular assemblies. Você pode também passar por pacotes NuGet de terceiros, conforme exibido na Figura 2.

Você pode instalar o ClearScript através do NuGet
Figura 2 Você pode instalar o ClearScript através do NuGet

Inicializar o ClearScript

Será necessário fazer algum trabalho antes de poder usar o mecanismo de script de forma programada. Mas no final do dia, quando estiver totalmente configurado, o código na Figura 3 é tudo que você precisa para disparar a execução do código de script.

Figura 3 Código para disparar o código de script

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

O método Confirm pertence a classe presenter que suporta a exibição principal do aplicativo de amostra. Dispare o método clicando no botão Executar (visível na Figura 1). A classe SonoraConsole que você vê referenciada na lista é apenas meu próprio embrulho em volta das principais classes da biblioteca ClearScript.

A inicialização do mecanismo ClearScript ocorre quando o aplicativo inicia e está associado ao evento Startup da classe do aplicativo XAML:

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

A inicialização pode ser tão complexa e sofisticada como você gostaria, mas ela pelo menos precisa inicializar o mecanismo de script da sua linguagem selecionada. Você precisa tornar a instância do mecanismo de script disponível para outras partes do aplicativo. Aqui está uma possível abordagem:

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

Talvez você queira ler no arquivo de configuração qual linguagem de script deve ser habilitada dentro do aplicativo. Este é um possível esquema da configuração:

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

Quando você tiver uma instância de seu mecanismo de script escolhido pronta, é possível executar qualquer código JavaScript (ou VBScript) válido. Não há muito que possa ser feito em um cenário do mundo real até você estar armado com estas capacidades básicas.

Adicionar objetos passíveis de script

Todos os mecanismos ClearScript expõem uma interface programável através da qual você pode adicionar objetos passíveis de script ao ambiente de tempo de execução. Em particular, você usa o método AddHostObject, como:

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

Este método exige dois parâmetros. O primeiro parâmetro é o nome público que os criadores de scripts usarão para referenciar o objeto que está sendo publicado. O segundo parâmetro é apenas a instância do objeto. Olhando para o trecho de código anterior, em qualquer JavaScript ou VBScript você pode usar o nome “out” para invocar qualquer um dos métodos públicos disponíveis na interface SonoraOutput. Aqui está um exemplo no JavaScript que referencia o que está mostrado na Figura 1:

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

Como você pode saber, é uma prática comum no JavaScript nomear os membros de acordo com a convenção camelCase. Na programação do .NET, a convenção PascalCase é mais comum e também recomendada. Na minha implementação da classe SonoraOutput, eu optei deliberadamente por seguir a convenção JavaScript e chamei o método print em vez do Print, como seria o caso na programação do C# simples.

Com base na minha experiência, você não precisa saber de muito mais para começar no ClearScript. A maioria das vezes, você pode configurar um ambiente do ClearScript dentro de um aplicativo host com o objetivo principal de tornar disponível objetos específicos do aplicativo. Com frequência, esses são objetos feitos sob medida embrulhados em volta de objetos de negócios existentes e são mais agradáveis para serem usados dentro de um ambiente de script.

Os usuários principais de um ambiente ClearScript não são geralmente desenvolvedores que trabalham em tempo integral. Provavelmente são pessoas com alguma habilidades de desenvolvimento de software que achariam desnecessariamente complexo e importuno lidar com os detalhes completos das classes do .NET. O ClearScript permite que você exponha blocos maiores do .NET Framework diretamente no JavaScript e VBScript. Eu optei por ter objetos feitos sob medida criados para simplicidade extrema. Aqui está como você pode publicar no ClearScript um tipo ao invés de um objeto:

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

Quando você faz referência a um tipo, está fornecendo aos usuários o poder de criar de forma programada instâncias daquele tipo. Por exemplo, a linha anterior de código adiciona poder do objeto .NET DateTime ao ambiente de script. Agora o seguinte código JavaScript se torna possível:

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

Do código JavaScript, você está tirando vantagem de todo o poder dos métodos como o AddDays e AddHours. O que acontece se você desejar obter a diferença entre duas datas? Você pode fazer algo parecido com isto:

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

O objeto TimeSpan é manipulado corretamente e a expressão span.Days retorna apenas 1000. Isso ocorre devido à natureza dinâmica da linguagem do JavaScript, que determina dinamicamente o objeto denominado “span” que expõe um membro denominado Days. Se você deseja, ao invés, criar uma instância TimeSpan, é necessário primeiro deixar o mecanismo saber que ela está lá.

Para evitar expor um milhão de tipos diferentes, o ClearScript permite que você hospede um assembly inteiro. Esta é uma possível abordagem:

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

A palavra-chave dotnet agora se torna a chave para acessar quaisquer tipos e membros estáticos dentro do mscorlib e System.Core. Criar um objeto com data nova demora um pouco mais, mas em retorno você pode trabalhar explicitamente com os 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);

O trecho de código do JavaScript imprime o número 2 como resultado da soma de dois objetos TimeSpan distintos, cada um contando por 24 horas. O ClearScript não funciona bem com a sobrecarga de operador. Isso simplesmente não existe. Significa somar datas ou períodos de tempo, você tem que usar métodos como Add ou Subtract. Ele também oferece suporte para a reflexão.

Gerar a saída

A ferramenta que você vê na Figura 1 deve ser capaz de exibir algum resultado ao usuário. Por padrão, o objeto SonoraOutput adicionado ao mecanismo ClearScript apenas mantém um objeto StringWriter interno. Todos os textos processados pelo método print são na verdade escritos para o escritor subjacente. O conteúdo do escritor é exposto ao mundo externo através da classe SonoraConsole. Essa classe é o único ponto de contato entre o mecanismo ClearScript e o aplicativo host. O apresentador do aplicativo host apenas retorna o conteúdo de um escritor de cadeia de caracteres através de uma propriedade. Essa propriedade é então associada a um TextBlock na interface do usuário do WPF. O método print escreve para a interface do usuário através de um escritor de cadeia de caracteres. O método clr limpa o buffer e a interface do usuário.

Salvar em um arquivo de texto

Meu cliente só precisava criar arquivos de texto, a maioria arquivos CVS. Isso é relativamente fácil de realizar. Tudo que fiz foi criar um método file e passar para ele algum conteúdo de texto diretamente. Eu poderia também deixar ele pegar algo que já estivesse impresso na tela e salvo no buffer interno. O aspecto mais problemático para lidar com quando se trata de arquivos é o nome e o local. Para tornar o script realmente rápido, precisa ser muito fácil criar e recuperar arquivos. Eu consegui ter duas pastas padrões—uma para entrada e outra para saída. Eu também suponho que todos os arquivos são TXT. Se nenhum nome for especificado para o arquivo, os arquivos assumem um nome padrão.

Essas suposições são possivelmente muito limitadas para alguns cenários, mas meu projeto foi simplesmente uma comprovação do conceito para uma ferramenta produzir arquivos, não armazená-los. Como a Figura 4 demonstra, eu poderia facilmente embrulhar o objeto XmlWriter em um belo componente e criar uma arquivo XML pelo script.

Create an XML File via Script
Figura 4 Criar um arquivo XML por meio do script

Conclusão

Qual é a razão para criar um arquivo XML por meio do script? Na verdade, é o mesmo que ter capacidades de script em alguns aplicativos empresariais. Você precisa do script porque deseja tarefas automatizadas. Em alguns casos, criar um texto local ou arquivos XML é somente o que você precisa. Talvez você possa executar consultas no SQL Server e importá-las para o CSV, mas isso exige acesso administrativo para o banco de dados de produção e, mais importante, as habilidades apropriadas. Eu mesmo teria problemas ao usar o xp_cmdshell para retirar arquivos de texto das consultas do SQL Server. Para um desenvolvedor, pode não ser difícil organizar alguns objetos locais fáceis de usar que já estão prontos para executar o script.

Meu cliente adorou essa ideia tanto quanto eu adorei usar o ClearScript. Ele me pediu para adicionar muito mais objetos ao ambiente dinâmico. Eu acabei adicionando uma camada de inversão de controle para configurar objetos para serem carregados na inicialização. Ele também está considerando o desenvolvimento do ClickOnce por toda a empresa para quando novas ferramentas forem lançadas.


Dino Esposito é o co-autor de “Microsoft .NET: Architecting Mobile Applications Solutions for the Enterprise” (Microsoft Press, 2014) e brevemente “Programming ASP.NET MVC 5” (Microsoft Press, 2014). Um evangelista técnico para as plataformas .NET Framework e Android na JetBrains e palestrante frequente em eventos do setor em todo mundo, Esposito compartilha sua visão do software em software2cents.wordpress.com e no Twitter em twitter.com/despos.

Agradecemos aos seguintes especialistas técnicos pela revisão deste artigo: Equipe do Microsoft ClearScript