sexta-feira, 16 de abril de 2010

Enviar uma imagem no Silverlight do cliente ao serviço

Precisava implementar a carga de uma imagem no meu cadastro em Silverlight. No meu serviço WCF, tem as operações que enviam e recebem as entidades do Entity Framework geradas com o modelo Self-Tracking Entities. A propriedade imagem é um byte[].

Mas como fazer um binding do byte[] para um controle Image? Eu nem tinha trabalhado com imagens ainda, que dirá enviar uma pro serviço. Vamos então à como eu consegui chegar na solução.

Primeiro, como fazer binding? Bom, a Image possui um atributo Source, que pode receber um objeto BitmapImage. Como eu recebo um objeto byte[], eu precisava instanciar uma BitmapImage desta forma. Como meu cadastro não tinha nenhuma imagem ainda, tive que implementar a carga primeiro. Aí começou o drama. O Silverlight implementa um modelo de sandbox para execução no browser, que ao menos pra me atrapalhar, funcionou redondinho. Não posso chamar métodos de System.IO por exemplo. Então veio a calhar o IsolatedStorageFile. Descobri o caminho no file system, xcopy umas imagens pra lá, e voilá! Já posso ver umas imagens na tela.



Agora, não seria prático acessar somente a isolated storage. Então, próximo passo. O Silverlight tem um componente OpenFileDialog, que permite acessar qualquer imagem do sistema de arquivos, semelhante ao que se tem em HTML também. A segurança está no fato de que é o usuário que escolhe o caminho do arquivo. Basicamente qualquer arquivo está na lista-negra. Arquivos na isolated storage e arquivos selecionados pelo usuário são inseridos na lista-branca.

Fui descobrir sem-querer querendo que a dialog impõe outra restrição de segurança: dialogs somente podem ser iniciadas pelo usuário. Fazendo debug, eu encontrei este erro. O presenter exibe a dialog. (Nota para mim mesmo: rever isto, pois o presenter não deve usar um recurso puramente da view). Mas quem chama o presenter, através de evento, é o handler do Button.Click, e o call stack está lá pra não me deixar mentir. O problema, vejam só, é que coloquei um breakpoint na linha do ShowDialog(), isto faz o Silverlight se confundir. Botei o breakpoint depois e tudo funcionou.

A carga da imagem é feita assim:

    // ClientePresenter: IClientePresenter
    void view_CarregarLogo()
    {
      OpenFileDialog dlg = new OpenFileDialog();
      dlg.Filter = "Imagens (*.png)|*.png";
      // nunca coloque breakpoint na linha a seguir!!
      if (dlg.ShowDialog() == true)
      {
        using (FileStream file = dlg.File.OpenRead())
        {
          byte[] bytes = new byte[file.Length];
          file.Read(bytes, 0, bytes.Length);
          view.LogoDoCliente = bytes;
        }
      }
    }

    // EditCliente: IClienteView
    public byte[] LogoDoCliente
    {
      get { return ClienteSelecionado.Logo; }
      set
      {
        AlterarLogoNaTela(value);
        // modificar entidade
        ClienteSelecionado.Logo = value;
      }
    }

    private void AlterarLogoNaTela(byte[] value)
    {
      if (value != null && value.Length != 0)
      {
        var bmp = new BitmapImage();
        using (MemoryStream strm = new MemoryStream(value))
        {
          bmp.SetSource(strm);
        }
        // Image no XAML
        LogoImage.Source = bmp;
      }
      else
      {
        LogoImage.Source = null;
      }
    }

Só falta enviar para o WCF. Funcionou de cara, pois carregava a imagem para um byte[] e usava isto para atualizar tanto a tela como a propriedade na entidade. Mas dava problema a mandar imagens maiores de aproximadamente 16KB. Aí entra em cena os limites do WCF. Veja um exemplo de como configurar o .config:

<binding name="BasicHttpBindingConfig" maxBufferSize="655360"
     maxReceivedMessageSize="655360">
  <readerQuotas maxArrayLength="65536"/>
</binding>


<serviceBehaviors>
  <behavior name="">
   <dataContractSerializer maxItemsInObjectGraph="65536"/>
  </behavior>
</serviceBehaviors>

Resumo:
  • Não coloque breakpoint na linha que exibe uma dialog
  • Não chame métodos com o atributo SecurityCriticalAttribute
  • Não acesse o sistema de arquivos sem solicitar o arquivo via file dialog
  • Não acesse um serviço WCF sem configurar reader quotas, MaxItemsInObjectGraph
  • Carregue os bytes da imagem para um MemoryStream, instancie um BitmapImage, e chame SetSource com a stream
  • O BitmapImage aceita uma stream com um PNG, já com BMP não funcionou
  • Use um padrão arquitetural como MVP, é muito bom mesmo

Nenhum comentário:

Postar um comentário