22 maio, 2009

Interação com Silverlight – Parte 1 – Passando Parâmetros

Há diversas formas de interagir e trocar informações com aplicações Silverlight mas não há muita informação, principalmente em português, sobre como elas funcionam e como utilizá-las. Por conta disso, hoje eu começo uma série de posts com dicas de como interagir com aplicações Silverlight. No final deste post eu vou colocar links para todos os outros posts desta série, conforme eles forem publicados.

Neste post vou falar sobre passagem de parâmetros.

Há diversos cenários onde sua aplicação Silverlight necessitará receber parâmetros externos para poder funcionar. Por exemplo: Você pode desenvolver um vídeo player e quando for colocá-lo em sua página terá que passar como parâmetro a url do vídeo que o player irá exibir.

A forma de fazer isso em Silverlight é muito semelhante à abordagem do Flash. Como vocês já devem saber a essa altura, o Silverlight (assim como o Flash) é um plugin do browser que é colocado na página utilizando a tag <object>. Essa tag object pode conter uma série de tags <param> que possuem propriedades name e value e servem para configurar o plugin. Em Flash, quando é necessário passar algum parâmetro é utilizado o <param>flashvars” e os parâmetros são passados de forma semelhante a parâmetros passados na url para uma página .aspx. Em Silverlight é utilizado o <param>initParams”, onde são passados chaves e valores separados por “,'” (vírgula). Atualmente há uma limitação nessa forma de passar parâmetros pois não há um encode automático de “,” ou “=” então, se o seu parâmetro tiver algum desses caracteres será necessário encodá-los manualmente de alguma forma.

Segue abaixo um exemplo de um player de vídeo em Silverlight, onde são passados como parâmetros o nome do arquivo de vídeo e um segundo parâmetro informado o modo de auto play:

<object width="100%" height="100%" data="data:application/x-silverlight-2," type="application/x-silverlight-2">
    <param name="source" value="/ClientBin/player.xap"/>
    <param name="initParams" value="video=meu-video.wmv,autoplay=false"/>
</object>

Em páginas .aspx pode ser utilizado o server control do Silverlight, como no exemplo abaixo:

<asp:Silverlight runat="server" Source="~/ClientBin/player.xap" Width="100%" Height="100%" InitParameters="video=meu-video.wmv,autoplay=false" />

Mas aí você me pergunta: Como eu faço para ler esses parâmetros?

Em páginas .aspx, quando passamos parâmetros na url utilizamos o Request[“nome_do_parametro”] para fazer a leitura. No Silverlight é um pouco diferente. Os parâmetros são encarados como sendo informações de inicialização da aplicação e, portanto, só há um lugar onde eles podem ser acessados que é a partir de um event handler do evento Startup da aplicação (a classe App do seu projeto Silverlight). Abaixo segue um exemplo de código para ler esses parâmetros:

public static void Application_Startup(object sender, StartupEventArgs e) {
    this.RootVisual = new Page();

    string movie = e.InitParams["video"];
    bool autoplay = false;
    bool.TryParse(e.InitParams["autoplay"], out autoplay);
}

Mas agora surge outro problema: Como fazemos para o resto do código da nossa aplicação ter acesso a esses valores?

Isso é fácil: Podemos criar propriedades estáticas na nossa classe App e acessá-las de qualquer lugar no nosso aplicativo. Segue abaixo outro exemplo:

public static string MovieFile { get; set; }

public static bool AutoPlay { get; set; }

public static void Application_Startup(object sender, StartupEventArgs e) {
    this.RootVisual = new Page(); 

    App.MovieFile = e.InitParams["video"]; 
    App.AutoPlay = bool.Parse(e.InitParams["autoplay"]);
}

Mas há uma coisa que me incomoda nesse código: Para cada parâmetro passado será necessário eu ler, efetuar o parse e atribuir o resultado à propriedade. Isso é tranquilo quando estamos falando de 2 ou 3 parâmetros string, mas começa a ficar complicado quando há muitos parâmetros ou quando os parâmetros são de tipos diferente de string, onde se torna necessário fazer parse. Em casos assim o código desse event handler poderia ficar grande muito rapidamente.

Então eu decidi pensar em como isso poderia se tornar mais fácil. Como eu gostaria que esses parâmetros funcionassem? Seria legal se eu pudesse simplesmente criar propriedades na minha classe App e marcá-las com algum atributo que indicasse que a propriedade deve ser inicializada a partir do valor passado no InitParams. Então eu pesquisei um pouco de vi que isso era de fato possível, utilizando um pouco de Reflection. Foi então que eu escrevi a classe abaixo:

public class InitParamAttribute : Attribute {
   public string ParamName { get; private set; }

   public InitParamAttribute(string paramName) {
      this.ParamName = paramName;
   }

   public static void AppStartup_Handler(object sender, StartupEventArgs e) {
      Type t = sender.GetType();
      var props = t.GetProperties();
      foreach (var p in props) {
         foreach (var ca in p.GetCustomAttributes(false)) {
            var a = ca as InitParamAttribute;
            if (a != null && !string.IsNullOrEmpty(a.ParamName) && p.CanWrite && a is InitParamAttribute) {
               if (e.InitParams.ContainsKey(a.ParamName)) { 
                  object valor = Convert.ChangeType(e.InitParams[a.ParamName], p.PropertyType, CultureInfo.InvariantCulture);
                  p.SetValue(sender, valor, null);
               }
            }
         }
      }
   }
}

O que essa classe faz é ler, usando Reflection, a classe App da aplicação e listar todas suas propriedades públicas. Para cada propriedade pública que estiver marcada com o atributo InitParam, ela vai verificar se o parâmetro correspondente foi passado, executar o parse do valor e atribuir à propriedade.

Usando esta nova classe, agora eu posso mudar o código da minha classe App para o seguinte:

[InitParam("video")]
public static string MovieFile { get; set; }

[InitParam("autoplay")]
public static bool AutoPlay { get; set; }

public App() {
    this.Startup += this.Application_Startup;
    this.Exit += this.Application_Exit;
    this.UnhandledException += this.Application_UnhandledException;
    this.Startup += InitParamAttribute.AppStartup_Handler;
    InitializeComponent();
}

public static void Application_Startup(object sender, StartupEventArgs e) {
    this.RootVisual = new Page();
}

Agora basta acessar essas propriedades de qualquer lugar do meu aplicativo Silverlight, utilizado por exemplo App.MovieFile. Se eu quiser passar mais parâmetros para a aplicação, basta criar mais propriedades e marcá-las com esse atributo.

É claro que é possível melhorar o código apresentado aqui. Umas das melhorias poderia ser, por exemplo, informar se o parâmetro é obrigatório ou não e lançar uma excessão caso seja obrigatório e não tenha sido passado, mas do jeito que está já serve para ilustrar como passar e utilizar parâmetros em aplicações Silverlight de forma eficiente e sem perder tempo escrevendo código de validação e tratamento de input.

Espero que este pequeno tutorial seja útil. Em breve darei continuidade a esta série escrevendo sobre outras formas de interagir com aplicações Silverlight.

Dicas sobre interação com aplicações Silverlight

  • Interação com Silverlight – Parte 1 – Passando Parâmetros