Impacto de Timeouts no Proxy WCF

by Israel Aece 5. February 2010 08:32

Como comentei neste post, as exeções influenciam diretamente na vida útil do proxy de um serviço WCF. Da mesma forma, quando eventuais timeouts acontecem no serviço, isso também faz com que o proxy seja movido para um estado falho, mas não diretamente.

Isso acontece para aqueles bindings que suportam sessão, onde depois de um tempo de inatividade, a sessão que é mantida do lado do serviço para aquele cliente, será expirada, fazendo com que o canal de comunicação do serviço entre em um estado falho, não podendo mais receber requisições.

Neste momento, o proxy correspondente ainda continua aberto e íntegro, até que ele envie uma próxima requisição até o serviço que está falho, e que por sua vez, irá disparar uma exceção e retornará uma fault para o cliente, que ao recebê-la, irá mover o proxy para um estado falho e, consequentemente, nenhuma outra requisição poderá ser realizada a partir daquele proxy, a menos que você o reconstrua, podendo utilizar a mesma técnica mostrada no post anterior.

Tags: , ,

WCF

Impacto de Exceções no Proxy WCF

by Israel Aece 4. February 2010 11:17

Uma das preocupações que devemos ter em qualquer tipo de desenvolvimento é com o tratamento de erros. Eu já comentei neste artigo e vídeo as possibilidades que temos para fazer isso dentro do WCF, utilizando suas próprias características de interceptação, transformação (promoção) e propagação das exceções para faults.

Já sabemos que se conhecermos todos os eventuais problemas que podem acontecer, podemos já definí-los no contrato do serviço (através do atributo FaultContractAttribute), para que assim o cliente possa ser notificado do que realmente aconteceu. Só que ainda há uma questão que não foi tratada, que é justamente como fica o estado do proxy depois que a falha acontece no serviço?

A resposta para essa pergunta dependerá de qual tipo de exceção que está sendo disparada, e também de qual binding está sendo utilizado por aquele endpoint. Em grande parte dos cenários, vemos que o desenvolvedor utilizar a instância de um mesmo proxy para efetuar várias chamadas para o mesmo serviço, não importando se é ou não para uma mesma operação.

Envolver as chamadas para as operações dentro de um bloco try/catch, evita apenas que a aplicação cliente saiba se comportar quando um determinado erro ocorre. Mas dependendo do que aconteceu no serviço, você não conseguirá reutilizar a mesma instância do proxy. Depois da falha, qualquer tentativa de comunicacão não chegará mais até o serviço, resultando assim naquela famosa exceção do tipo CommunicationObjectFaultedException, com a seguinte mensagem:

System.ServiceModel.CommunicationObjectFaultedException: The communication object, System.ServiceModel.Channels.ServiceChannel, cannot be used for communication because it is in the Faulted state

Digamos que você não está preocupado com as exceções que ocorrem dentro do seu serviço. Dentro de uma determinada operação, uma exceção CLR (ArgumentNullException, IndexOutOfRangeException, etc.) é disparada. No exemplo abaixo, estou efetuando o teste para saber se o parâmetro é ou não nulo, e sendo, uma exceção do tipo ArgumentNullException está sendo disparada:

public string Ping(string value)
{
    if (value == null)
        throw new ArgumentNullException("value");

    return value;
}

Como exceções não tratadas do lado do serviço são consideradas um risco, o canal de comunicação (proxy) não ficará mais disponível. Para os bindings que suportam sessão (NetTcpBinding, WSHttpBinding, etc.), isso quer dizer que se você tentar invocar uma segunda requisição, ele estará em um estado inválido (Faulted), o que obrigará a você recriar o proxy (instanciar novamente) para depois utilizá-lo. A solução para este caso, já foi comentada nos artigos que referencie acima, que é trabalhar explicitamente com a classe FaultException (ou até mesmo FaultException<T>):

public string Ping(string value)
{
    if (value == null)
        throw new FaultException("value");

    return value;
}

Desta forma, o canal de comunicação não será afetado, e seguramente você poderá utilizá-lo para efetuar novas requisições. Já aqueles bindings que não suportam sessão, como é o caso do BasicHttpBinding ou do WebHttpBinding (utilizado para REST/AJAX), não serão danificados, mesmo se você não estiver se preocupando com o uso da classe FaultException.

Como muitas vezes as operações recorrem a outras classes, e que muitas você não tem acesso à elas, então você dificilmente conseguirá mapear todas as exceções que podem acontecer. Para garantir que o proxy consiga se "restaurar" de um estado falho, você pode recorrer ao evento Faulted, exposto pela interface ICommunicationObject e também pela classe ClientBase<TChannel>. Este evento é disparado quando o proxy entre em estado falho, e assinando este evento, você poderia reconstruir o proxy no exato momento, como eu mostro no código abaixo:

ChannelFactory<IContrato> factory = 
    new ChannelFactory<IContrato>(new NetTcpBinding(), new EndpointAddress("net.tcp://localhost:3732/srv"));

IContrato proxy = factory.CreateChannel();
((ICommunicationObject)proxy).Faulted += (o, e) => proxy = factory.CreateChannel();

Tags: , ,

WCF

Usando LINQ To SQL com WCF

by Israel Aece 2. February 2010 21:16

Para aqueles que trabalham com LINQ To SQL e querem expor as entidades geradas pela ferramenta via WCF, terá que tomar alguns cuidados. Quando essas entidades não mantém um relacionamento, algo que é bem díficil, você consegue retorná-las a partir das operações que o serviço irá disponibilizar.

O problema começa a aparecer quando você deseja enviar para os clientes, entidades que possuem relacionamentos com outras entidades. Um exemplo clássico e que ilustra bem o cenário, é quando temos categorias e produtos, onde cada produto deverá pertencer à somente uma única categoria. Neste caso, temos uma relação direta entre elas. Mas o ponto que torna isso difícil é que o LINQ To SQL cria uma relação bidirecional entra elas. Isso quer dizer que a entidade Categoria terá uma propriedade chamada Produtos, que como o próprio nome diz, retorna a coleção dos produtos daquela categoria; além disso, a classe Produto expõe uma propriedade chamada Categoria, que retorna a instância de uma categoria em qual o produto está contido.

Quando falamos em orientação à objetos, isso é perfeitamente válido e comum. O problema ocorre quando você tenta serializar essa estrutura a partir do WCF. Como eu comentei neste outro post, o WCF tem um comportamento diferente quando há referências circulares, e não conseguirá fazer a serialização porque ele ficará em uma espécie de loop, pois com há referência bidirecional, ao serializar uma categoria, ela serializa os respectivos produtos, e para cada produto a sua respectiva categoria, e para esta categoria os seus produtos, e por aí vai. O serviço roda sem maiores problemas, mas você terá uma exceção quando quando o cliente tentar acessá-lo.

Analisando a imagem abaixo, podemos visualizar a estrutura das classes que compõem o exemplo, e logo ao lado, você pode reparar nas propriedades do arquivo (superfície) DBML, verá que existe uma propriedade chamada Serialization, que pode receber apenas dois valores: None e Unidirectional. O primeiro deles permite que as propriedades das entidades sejam serializadas de acordo com as regras impostas pelo serializador padrão do WCF, que graças a possibilidade de serializar qualquer propriedade, mesmo que elas não estejam decoradas com os atributos DataContractAttribute e DataMemberAttribute (POCO). A segunda opção, Unidirectional, faz com que ele somente consiga serializar o relacionamento em uma única direção para evitar as referências circulares. No nosso caso, teremos a entidade Categoria com a propriedade Produtos, mas a classe Produto não terá uma propriedade que define a sua categoria.

Alterando a opção de serialização para Unidirectional, a forma que você efetua a consulta também terá que mudar. Isso fará com que o LINQ To SQL não consiga trazer os dados (produtos) relacionados aquela categoria, algo que é transparente quando estamos utilizando o LINQ To SQL diretamente. Para conseguir fazer com que os dados relacionados também sejam carregados, temos que recorrer a classe DataLoadOptions, como é mostrado abaixo:

public Categoria[] RecuperarCategorias()
{
    using (DBContextDataContext ctx = new DBContextDataContext())
    {
        DataLoadOptions opts = new DataLoadOptions();
        opts.LoadWith<Categoria>(c => c.Produtos);
        ctx.LoadOptions = opts;

        return (fromin ctx.Categorias select c).ToArray();
    }
}

Mas como disse acima, isso funcionará mas você perderá a navegação bidirecional. Felizmente, podemos recorrer à propriedade boleana IsReference, que é exposta pelo atributo DataContractAttribute, definindo isso na classe Categoria. Isso permitirá a criação da navegação bidirecional, mas há um trabalho manual a ser feito para que isso funcione. Quando você muda a propriedade Serialization para None, nenhuma das propriedades é decorada com o atributo DataContractAttribute/DataMemberAttribute; já se definir essa propriedade para Unidirectional, as propriedades que são problemáticas, não estarão decoradas com o atributo DataMemberAttribute.

Sendo assim, o exemplo final fica como é mostrado abaixo, conseguindo ter no cliente, a navegação bidirecional. Obviamente que alguns membros foram omitidos por questões de espaço.

[Table(Name = "dbo.Categoria")]
[DataContract(IsReference = true)]
public partial class Categoria
{
    [DataMember]
    [Column(...)]
    public int CategoriaId

    [DataMember]
    [Column(...)]
    public string Nome

    [DataMember]
    [Association(...)]
    public EntitySet<Produto> Produtos
}

[Table(Name = "dbo.Produto")]
public partial class Produto
{
    [Column(...)]
    public int ProdutoId

    [Column(...)]
    public int CategoriaId

    [Column(...)]
    public string Nome

    [Association(...)]
    public Categoria Categoria
}

Tags: , ,

Data | WCF

Gerenciamento de Channels

by Israel Aece 1. February 2010 12:38

Algum tempo atrás eu falei sobre os internals de um proxy WCF. Como eu havia dito, existe um grande overhead quando criamos um novo proxy, que está condicionado as complexidades que podem ou não estarem habilitadas (segurança, transações, mensagens confiáveis, etc.).

Como disse naquele mesmo post, o ponto mais custoso da criação, que é a factory (ChannelFactory<TChannel>), está sendo reutilizada, em nível de AppDomain, ou seja, mesmo que você não mantenha a instância do proxy gerado (ClientBase<TChannel>) pela IDE do Visual Studio ou pelo utilitário svcutil.exe, o runtime do WCF irá reciclar a factory, e em seguida, a colocará em um cache.

Quando estabelecemos a comunicação entre o cliente e o serviço, utilizamos um canal de comunicação, referido também como channel. Estes são utilizados por nós, na maioria das vezes implicitamente, para efetuar as requisições para o respectivo serviço. Na verdade, é a factory que fornece um método chamado CreateChannel, que retorna a instância de um TransparentProxy. Este tipo nos permite efetuar uma conversão para o contrato exposto pelo documento WSDL, e assim invocar as operações como se elas fossem simples métodos locais. Se você abrir a classe que é gerada quando você faz a referência à um serviço, verá que dentro das operações sempre há uma chamada para a propriedade Channel, que por sua vez, irá até o método CreateChannel.

Como a parte custosa já está sendo reutilizada de forma performática pelo WCF, as vezes surge a dúvida se devemos ou não manter um outro cache, mas este para armazenar as instâncias dos channels que são retornados pelo método CreateChannel, mas que a classe ClientBase<TChannel> não se preocupa com isso. O objeto retornado por esse método implementa a interface ICommunicationObject, que fornece entre vários membros, um método autoexplicativo chamado Close, que é responsável por encerrar mover o estado do proxy corrente para Closed e também descartar os recursos que ele utiliza.

Assim como todo recurso caro, é importante você sempre descartá-lo quando não precisar mais dele. Isso também é o caso dos channels. Mas é importante você analisar cuidadosamente o cenário. Se você faz chamadas subsequentes, não convém a todo momento invocar o método CreateChannel para criar um novo canal; reutilize-o durante essas chamadas, e somente feche-o quando realmente não precisar mais dele, dentro daquele escopo.

Da mesma forma, antes de descartá-lo completamente, analise se você não pode manter um cache para estes channels. Fazer pooling de channels é uma boa alternativa para diminuir ainda mais os custos de criação deles. Você pode manter os channels desde que:

  • Não há um contexto de segurança exclusivo para cada usuário (SCT).
  • O mesmo channel não é utilizado por múltiplas threads ao mesmo tempo.
  • Não há manutenção de estado.

Na primeira situação o problema acontece porque as credenciais são definidas durante a criação da factory, e não podem mais serem alteradas. Já o segundo cenário, acontece porque os channels são thread-safe, ou seja, eles não dão suporte ao envio de mensagem de forma concorrente. Se em uma thread A você está utilizando o channel C1 para mandar uma mensagem grande, a thread B que utilizará o mesmo channel C1, somente conseguirá enviar a mensagem depois que a thread A finalizar. A última das situações implica quando você mantém uma sessão entre o cliente e o serviço, pois ela está fortemente ligada ao channel correspondente.

Tags: ,

WCF

Programação Assíncrona no ASP.NET MVC

by Israel Aece 25. January 2010 10:38

A Microsoft introduziu na versão 2.0 do ASP.NET WebForms uma funcionalidade chamada de páginas assíncronas, assunto qual já comentei bastante por aqui, e também fiz uma palestra no TechEd 2008 à respeito desse mesmo assunto. Para recapitular, e resumidamente falando, quando uma requisição chega para o IIS, o mesmo entrega para o ThreadPool da CLR, que utilizará uma thread para efetivamente executar aquela requisição.

Por padrão, as páginas são sempre síncronas, ou seja, enquanto aquela requisição não for finalizada, a thread não será liberada. O problema disso é que muitas vezes, as páginas executam processos pesados, como por exemplo, acesso à serviços, consultas em base de dados, etc. Essas tarefas são consideradas I/O-bound, ou seja, são tarefas que não dependem da máquina local, mas sim do processamento do computador remoto que hospeda o serviço/banco de dados, da latência da rede, etc. Neste tempo que a thread fica aguardando esse processo de I/O finalizar, ela poderia estar servindo outras requisições, também ASP.NET, mas que não fazem necessariamente acesso à recursos deste tipo, como por exemplo, páginas institucionais. Dependendo da demanda, é comum o usuário receber no navegador erros com as seguintes mensagens de erro: Server Unavaliable ou Server Too Busy (503).

As páginas assíncronas resolvem esse tipo de problema, ou seja, quando encontrar uma tarefa deste tipo, a executam em uma thread de I/O, devolvendo a thread para o ThreadPool, e dando a chance dela atender outras requisições. Quando o processo remoto finalizar, a thread de I/O é retornada com o resultado e, novamente, uma thread é apanhada do ThreadPool para finalizar a requisição, e que na maioria das vezes, irá renderizar o resultado.

Por ser algo que aumenta consideravelmente a performance, a Microsoft está introduzindo este mesmo recurso no ASP.NET MVC. A partir da versão 2.0 do mesmo, teremos a possibilidade de criar ações assíncronas. Aplicações MVC que estão sujeitas à uma grande quantidade de requisições, podem fazer as execuções das ações de forma assíncrona, trabalhando de forma bem semelhante ao WebForms, e dando também ao MVC o mesmo benefício.

O primeiro passo para a criação de ações assíncronas, é fazer com o controller herde da classe abstrata AsyncController e não apenas de Controller. Essa classe fornecerá toda a infraestrutura para que as ações sejam executadas assincronamente, mas é importante dizer que mesmo que o controller herde de AsyncController, ele ainda pode continuar executando ações síncronas. Ao contrário do modelo síncrono, ações assíncronas dependem de um par de métodos, sendo um que inicia a tarefa custosa, e o segundo que será disparado quando o processo for finalizado. Atualmente é necessário sufixar o nome da ação com os respectivos sufixos: "Async" e "Completed". Abaixo temos um exemplo de como fica a estrutura de um controller assíncrono:

public class UsuariosController : AsyncController
{
    public void ListagemAsync(int quantidade)
    {
        //...
    }

    public ActionResult ListagemCompleted(Usuario[] usuarios)
    {
        //...
    }
}

Repare que o primeiro método é definido como void, pois quem retorna o resultado para a View é o método que é disparado quando o processo for finalizado. Um outro detalhe importante é que o método sempre será referenciado ou acessado no navegador como "Listagem" e nunca como "ListagemAsync", pois os sufixos são somentes utilizados pelo runtime do ASP.NET.

Ao herdar da classe AsyncController, novas propriedades estão a nossa disposição, e uma delas é a AsyncManager, que retorna a instância de uma classe com o mesmo nome. Como o próprio nome diz, ela é a responsável por gerenciar as operações assíncronas. Essa classe fornece três propriedades: OutstandingOperations, Parameters e Timeout. A primeira delas, OutstandingOperations, retorna a instância de uma classe chamada OperationCounter, onde essa classe controla a quantidade de operações que foram inicializadas pela respectiva ação. Já a propriedade Parameters, serve como um dicionário de dados, que permite passar informações para o método de finalização (como o resultado, por exemplo) e, finalmente, a propriedade Timeout, onde podemos definir um número inteiro que representa a quantidade de milisegundos (padrão de 45000 (45 segundos)) que o ASP.NET irá aguardar até que a operação seja finalizado.

Como sabemos, o .NET Framework fornece duas formas para trabalho assíncrono: modelo APM (métodos Begin/End) ou o modelo de eventos. O MVC suporta as duas formas de trabalho, mas a implementação para cada uma delas é ligeiramente diferente. Independentemente de qual técnica você utilize para invocar, o método que é disparado quando o processo assíncrono é finalizado não mudará em nada. Abaixo temos a sua implementação, e podemos notar que ele nada sabe sobre questões assíncronas.

public ActionResult ListagemCompleted(Usuario[] usuarios)
{
    ViewData["Usuarios"] = usuarios;
    return this.View();
}

O próximo passo é codificar o método ListagemAsync, e vamos utilizar inicialmente o modelo de programação assíncrona do .NET (APM). Como exemplo, vamos consumir um serviço WCF que foi referenciado na aplicação. Lembre-se que ao referenciar um serviço WCF, por padrão, ele não traz as versões assíncronas das operações; para poder habilitá-las, consulte este artigo.

Vamos então instanciar o proxy para estabelecer o canal de comunicação entre a aplicação e o serviço WCF. Podemos notar que temos que obrigatoriamente invocar os métodos Increment e Decrement, expostos pela propriedade OutstandingOperations, para especificar a quantidade de operações assíncronas que estão em andamento. Depois disso, devemos inicializar a operação assíncrona, através do método BeginRecuperarUsuarios. De acordo com o modelo APM, além dos parâmetros exigidos pelo método em si, temos que informar um callback, que nada mais é do que o método que será executado quando o processo assíncrono for finalizado.

Note no código abaixo que dentro do método de callback, estamos recuperando o resultado (EndRecuperarUsuarios) e armazenando dentro da propriedade Parameters a coleção de usuários. O valor colocado dentro deste dicionário será passado para o método ListagemCompleted, através do parâmetro "usuarios". Em seguida estamos também decrementando o contador de operações assíncronas. Note que tudo o que foi descrito neste parágrafo, está sendo executado dentro do método chamado Sync, também fornecido pela propriedade AsyncManager. Isso é necessário para garantir que este código e, um pouco mais tarde, a execução do método ListagemCompleted, sejam disparados em uma thread que o ASP.NET terá o controle. Se você não se atentar à isso e tentar executar esse código, ainda estará em uma thread de I/O, fazendo com que o contexto do HTTP (HttpContext.Current) esteja nulo e, consequentemente, não conseguirá acessar grande parte dos recursos que precisa para exibir o resultado.

public void ListagemAsync(int quantidade)
{
    ServicoDeUsuarios proxy = new ServicoDeUsuarios();
    this.AsyncManager.OutstandingOperations.Increment();

    proxy.BeginRecuperarUsuarios(quantidade, ar =>
    {
        AsyncManager.Sync(() =>
        {
            this.AsyncManager.Parameters["usuarios"] = proxy.EndRecuperarUsuarios(ar);
            this.AsyncManager.OutstandingOperations.Decrement();
        });
    }, null);
}

Depois de visualizar a implementação baseada no modelo APM, temos agora o modelo de eventos. Neste caso não precisamos envolver o método Sync, pois o evento que determina que o processo foi finalizado já acontece dentro da thread do próprio ASP.NET. Tudo o que precisamos fazer é se vincular à este evento, e dentro dele armazenar o resultado na propriedade Parameters e decrementar o contador, tudo de forma bem parecida ao que vimos acima. Apenas para iniciar o processo assíncrono, você deverá invocar a versão assíncrona do método que é gerado durante a criação do proxy do WCF, que terá sempre o nome da operação sufixada com a palavra "Async".

public void ListagemAsync(int quantidade)
{
    ServicoDeUsuarios proxy = new ServicoDeUsuarios();
    this.AsyncManager.OutstandingOperations.Increment();

    proxy.RecuperarUsuariosCompleted += (sender, e) =>
    {
        this.AsyncManager.Parameters["usuarios"] = e.Result;
        this.AsyncManager.OutstandingOperations.Decrement();
    };

    proxy.RecuperarUsuariosAsync(quantidade);
}

Observação: Qual dos dois modelos utilizar? Isso vai depender da API que está sendo chamada dentro do controller/ação assíncrono. Se ela suportar os dois modelos, então você pode escolher um deles. Mas há situações que não temos esse luxo, como por exemplo, quando queremos invocar uma consulta no SQL Server usando o ADO.NET tradicional, ou até mesmo ler o conteúdo de um arquivo no disco. Essas classes apenas fornece o modelo APM, com um par de métodos Begin/End.

A Microsoft ainda disponibilizou dois atributos: AsyncTimeoutAttribute e NoAsyncTimeoutAttribute. O primeiro deles disponibiliza uma propriedade chamada Duration, que como falamos acima, recebe a quantidade de milisegundos que determina o timeout. Já o segundo atributo deve ser aplicado quando você quer deixar isso indefinido. Independentemente de qual irá utilizar, eles devem ser aplicados sempre ao método que está sufixado com a palavra "Async", assim como vemos abaixo:

[AsyncTimeout(Duration = 30000)]
public void ListagemAsync(int quantidade)
{
    //...
}

Conclusão: É importante dizer que esse modelo de programação, apesar de tornar o código um pouco mais ilegível e poluído, traz um grande benefício em termos de performance e escalabilidade. Não pense que a página aparecerá no navegador do usuário enquanto a ação é processada, e quando ela for finalizada, aparecerá os dados na tela. Em termos visuais, você ainda terá o mesmo resultado, ou seja, enquanto a ação não for finalizada o navegador ficará bloqueado até que a requisição como um todo seja concluída, mas esta funcionalidade irá "desafogar" o ASP.NET.

Tags: ,

ASP.NET | Async

Debug em DataBinding do WPF

by Israel Aece 23. January 2010 10:30

Como eu escrevi aqui, o WPF fornece várias funcionalidades para databinding. Quando efetuamos a configuração de forma declarativa, a verificação para se certificar de que a propriedade que serve como origem das informações realmente existe, somente acontecerá durante a execução, mas o WPF não irá disparar qualquer exceção se ela não for encontrada. O binding simplesmente não funciona.

Para exemplificar, o código abaixo define a propriedade Content do controle Button com uma propriedade que não existe no TextBox. Esse código compilará sem problemas, mas durante a execução, o texto do botão sempre ficará vazio.

<TextBox Name="textBox1" />
<Button Name="button1" Content="{Binding ElementName=textBox1, Path=PropriedadeQueNaoExiste}" />

Assim como já acontece com o WCF, o Microsoft disponibilizou uma série de elementos de tracing que nos permitirá escolher aqueles que nos interessam para começar a monitorar eventuais informações que eles geram. Entre as várias opções, uma delas é a chamada System.Windows.Data, que é utilizada para catalogar mensagens relacionadas ao processo de databinding, incluindo casos como este, onde não é possível resolução do binding.

Para habilitá-lo, basta configurar o TraceSource como é mostrado abaixo, definindo qual nível de severidade das informações está interessado (TraceSwitch) e, finalmente, o local onde deseja salvar essas informações (TraceListener). O exemplo abaixo ilustra a configuração feita através do arquivo App.config da aplicação:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.diagnostics>
    <sources>
      <source name="System.Windows.Data" switchName="SourceSwitch">
        <listeners>
          <add name="textListener" />
        </listeners>
      </source>
    </sources>
    <switches>
      <add name="SourceSwitch" value="All"/>
    </switches>
    <sharedListeners>
      <add name="console"
           type="System.Diagnostics.ConsoleTraceListener"
           initializeData="false"/>
    </sharedListeners>
  </system.diagnostics>
</configuration>

O ConsoleTraceListener faz com que as informações sejam enviadas para a janela de Output do Visual Studio. Ao rodar o código XAML acima, vamos visualizar a seguinte mensagem:

System.Windows.Data Error: 39 : BindingExpression path error: 'PropriedadeQueNaoExiste' property not found on 'object' ''TextBox' (Name='textBox1')'. BindingExpression:Path=PropriedadeQueNaoExiste; DataItem='TextBox' (Name='textBox1'); target element is 'Button' (Name='button1'); target property is 'Content' (type 'Object')

Tags:

WPF

Utilizando Resources no WPF

by Israel Aece 23. January 2010 09:02

Qualquer tipo de aplicação geralmente precisa de arquivos adicionais, que servem como recursos extras para a mesma. Esses arquivos muitas vezes são do tipo Xml, simples textos, imagens, ícones, etc. Esses arquivos são colocados dentro do mesmo diretório da aplicação, e muitas vezes organizados através de sub-diretórios. Geralmente esse tipo de técnica define esses arquivos como arquivos de conteúdo, e são distribuídos juntamente com o assembly, mas estão fisicamente separados.

Desde as primeiras versões do .NET, para as aplicações construídas em cima da plataforma, podemos embutir um arquivo qualquer em um determinado assembly, fazendo com que este assembly contenha tudo o que ele precise. Um exemplo disso é o assembly System.Web.dll. Dentro dele há toda a infraestrutura do ASP.NET, incluindo os controles. Quando estamos desenvolvendo uma aplicação deste tipo, a ToolBox do Visual Studio exibe os controles disponíveis (novamente, aqueles que estão dentro do System.Web.dll), e ao lado uma imagem que representa o controle. Mas de onde vem essa imagem? Elas estão embutidas neste mesmo assembly, como você pode visualizar na imagem abaixo:

O WPF também possibilita embutir arquivos extras à nossa aplicação, compilando - ou não - juntamente com o assembly (exe). Imagine que você adiciona a referência para uma imagem qualquer no Solution Explorer. Se for até as propriedades da mesma, verá que existe uma propriedade chamada Build Action, que determina justamente o comportamento desta imagem durante a compilação. Entre as várias opções voltadas para o WPF, vamos analisar duas delas: a Resource e Content.

A primeira opção faz com que o arquivo em questão seja embutido no assembly, enquanto a segunda opção faz com que o arquivo seja mantido como um arquivo de conteúdo da aplicação, mas não o compila para dentro do assembly. Independentemente da forma que utiliza para o arquivo, sempre utilizaremos uma URI para acessá-lo, dependendo apenas do local que ela se encontra (embutida ou não). Por exemplo, se temos uma imagem chamada Logo.png e ela está marcada como Resource, você pode simplesmente fazer:

<Image Source="Logo.png" />

Como disse acima, é comum utilizar sub-diretórios para organizar os arquivos que fazem parte da aplicação. Quando cria uma pasta para armazenar esses arquivos, você deverá também mencionar o caminho ao referenciá-lo. Isso é necessário mesmo quando embutimos o arquivo no assembly, pois o WPF nomeia o recurso utilizando a estrutura de pastas. Abaixo temos o resultado da compilação dos recursos, onde acima temos um sub-diretório chamado Imagens e a imagem está dentro dele, enquanto abaixo temos a imagem colocada na raiz da aplicação.



A utilização das imagens que estão em sub-diretórios, pode ser feita da seguinte forma:

<Image Source="Imagens\Logo.png" />

É importante dizer que a forma de acesso aos recursos que utilizamos acima, é apenas uma espécie de atalho, pois isso é transformado em uma URI muito mais complexa, para que assim possa acessar o respectivo recurso. A URI completa para acessar o recurso deve ser: pack://packageUri/partPath. Transformando os exemplos anteriores nesta sintaxe, teríamos o seguinte:

<Image Source="pack://application:,,,/Logo.png" />
<Image Source="pack://application:,,,/Imagens/Logo.png" />

Caso você desejar acessar recursos que estão embutidos em outros assemblies, você deverá referenciá-lo com esta sintaxe, definindo explicitamente o nome do assembly que deseja extrair a informação. O exemplo ilustra como proceder para acessar a mesma imagem, mas que estará em um segundo assembly:

pack://application:,,,/BoletosBancarios;component/Imagens/Logo.png

Quando você não deseja embutir o arquivo no assembly, então o arquivo vinculado à aplicação deverá ser marcado como Content. Para acessar esse tipo de recurso, dependerá também de onde ele estará armazenado. Por exemplo, se ele estiver armazenado em algum diretório do disco, podemos referenciar diretamente:

<Image Source="C:\Temp\Logo.png" />

Quando Content é definido para o arquivo, você deve definir a propriedade Copy To Output Directory para Copy Always, para que o arquivo seja copiado para o mesmo diretório onde a aplicação está sendo gerada, para assim facilitar as referências relativas à ele.

Você ainda pode referenciar também outros tipos de arquivos que estão externos a sua aplicação, como por exemplo, aqueles que estão em algum ponto da internet, como por exemplo:

<Image Source="http://www.site.com.br/Imagens/Logo.png" />

O tipo de distribuição da aplicação influencia no caminho de acesso aos recursos, e ao invés de você deixar em hard-code, o WPF facilita isso com o uso de uma opção chamada de siteOfOrigin. Essa opção retorna valores diferentes, de acordo com a forma com que a aplicação foi originalmente instalada. Por exemplo, se a aplicação foi instalada através do Click Once, essa opção retornará a URL de onde a aplicação foi distribuída; se utilizar Windows Installer (MSI), essa opção refletirá o diretório raiz da aplicação.

Essa opção facilita bastante, já que independentemente da forma que a aplicação é entregue, você sempre conseguirá chegar até o recurso que está sendo solicitado. A sua utilização é simples, basta alterar o packageUri de application para siteOfOrigin, como mostrado no exemplo abaixo:

<Image Source="pack://siteOfOrigin:,,,/Logo.png" />
<Image Source="pack://siteOfOrigin:,,,/Imagens/Logo.png" />

E, finalmente, esse tipo de sintaxe também é a mesma para acesso via código:

this.image1.Source = new BitmapImage(new Uri("pack://application:,,,/Imagens/Logo.png"));
//Ou
this.image1.Source = new BitmapImage(new Uri("Logo.png"));

Conclusão: Os recursos que são adicionados à aplicação não estão resumidos ao que vimos aqui. Há também a possibilidade de embutir arquivos como MP3, AVI, etc., e tudo isso dependerá do quanto isso é viável. Além disso, os recursos também são largamente utilizados quando estamos construindo uma aplicação localizada, aquela que dá suporte à múltiplos idiomas, mas isso é matéria para um próximo artigo.

Tags:

WPF

DataBinding em WPF

by Israel Aece 22. January 2010 22:43

Já ouvimos falar muito sobre o termo DataBinding. Como sabemos, trata-se de um mecanismo para associar a informação de uma determinada origem à um determinado destino, criando uma dependência unidirecional ou bidirecional. Muitas vezes este termo está associado à alguma fonte de dados, que desejamos exibir suas respectivas informações na tela de uma aplicação qualquer.

Cada tecnologia implementa isso de uma forma diferente, com seus benefícios e possíveis limitações. O ASP.NET traz essa funcionalidade, onde você pode facilmente ligar uma fonte de dados, mas tem algumas limitações por conta de ser HTTP, que não mantém estado. Já o Windows Forms, fornece uma forma de databinding muito mais rico em termos de funcionalidades. A finalidade deste artigo é apresentar o databinding no WPF, exibindo suas funcionalidades, que facilitarão a forma com que lidamos com manipulações de UI.

A Microsoft incorporou no WPF uma forma muito mais evoluída para efetuar databinding, não se limitando apenas a vincular uma fonte de dados à controles, mas também permitindo que qualquer objeto preencha outro, incluindo controles. Isso quer dizer que poderemos definir o valor de uma propriedade de um controle com o valor de uma outra propriedade e de um outro controle. Tudo isso eliminando grande parte do código imperativo requerido pelo Windows Forms, pois a partir de agora, poderemos recorrer a código declarativo (XAML) para especificar essas "amarrações".

Grande parte da responsabilidade para fazer tudo isso funcionar, é a classe Binding, que está debaixo do namespace System.Windows.Data. Ela é responsável por manter o "canal de comunicação" entre a origem e o destino, e além disso, expõe uma série de propriedades que nos permite customizar o comportamento dessa comunicação. Entre as principais propriedades, temos:

  • ElementName: define o nome do elemento que servirá como fonte. Utilize esta propriedade quando desejar preencher uma outra propriedade com o valor de um controle do WPF.
  • Mode: determina a direção das informações.
  • NotifyOnSourceUpdated: valor boleano indicando se o evento SourceUpdated é disparado quando alguma atualização na fonte das informações ocorrer.
  • NotifyOnTargetUpdated: valor boleano indicando se o evento SourceUpdated é disparado quando alguma atualização no destino das informações ocorrer.
  • Path: espefica o nome da propriedade que será exibida.
  • RelativeSource: especifica uma fonte de forma relativa à posição do objeto atual.
  • Source: define o nome do objeto que servirá como fonte. Utilize esta propriedade quando desejar preencher com uma instância de um objeto.
  • XPath: a mesma finalidade da propriedade Path, mas define uma expressão XPath quando a fonte de informações for um arquivo Xml.

Note que nas propriedades acima, nós não temos uma propriedade que especifica qual propriedade no destino será carregada. Isso se deve, porque você aplicará a sintaxe de binding diretamente dentro da propriedade que você quer preencher. O exemplo abaixo mostra como podemos proceder para preencher um conteúdo de um controle Label com o texto de um Button:

<Button Name="button1" Content="Texto do Botão" />
<Label Name="label1" Content="{Binding ElementName=button1, Path=Content}" />

Alternativamente, você pode achar essa sintaxe um pouco ilegível, principalmente quando você tiver situações mais complexas. Se desejar, você pode recorrer à uma segunda forma de configurar o databinding, de forma hierárquica, onde você irá aninhar as configurações como um Xml tradicional, através de sub-elementos. O código abaixo ilustra esta segunda técnica:

<Button Name="button1" Content="Texto do Botão" />
<Label Name="label1">
    <Label.Content>
        <Binding ElementName="button1" Path="Content" />
    </Label.Content>
</Label>

Quando o modelo declarativa não é uma solução, pois você precisa dinamicamente determinar os databindings, você pode ainda utilizar o código C#/VB.NET para configurá-los. Tudo o que precisamos fazer é instanciar a classe Binding que falamos acima, e configurar as propriedades necessárias para que isso funcione, e que neste exemplo simples serão ElementName e Path. ElementName vai receber uma string com o nome do controle que servirá como origem, enquanto a propriedade Path, receberá a instância da classe PropertyPath, que em seu construtor você deverá especificar o nome da propriedade no objeto de origem, que quer que seja enviado para o destino.

Binding b = new Binding();
b.ElementName = "button1";
b.Path = new PropertyPath("Content");

this.label1.SetBinding(Label.ContentProperty, b);

Depois da instância da classe Binding configurada, utilizamos o método SetBinding do controle de destino, que no caso do exemplo é o Label. Além do Binding, esse método recebe a dependency property que receberá o valor da origem.

Carregando um Objeto

Acima vimos como podemos utilizar o databinding de uma forma diferente da qual estamos acostumado, que é através de controles de UI. Mas, o cenário mais comum é quando precisamos preencher um, ou vários controles de UI, com propriedades de um objeto. Eventualmente você tem um objeto que foi construído pela aplicação, e você precisa exibí-lo no formuário, distribuindo suas propriedades pelos controles do mesmo. O databinding também ajuda nisso, onde você pode especificar o tipo da classe, e o próprio WPF o instancia e, consequentemente, preenche os controles interessados, e tudo isso sendo feito declarativamente.

Ao invés de utilizar a propriedade ElementName, vamos agora recorrer à propriedade Source, que deve ser utilizada quando a origem se tratar de um objeto. Para exemplificar, vamos criar a instância da classe dentro dos resources do formulário, que nada mais é que uma coleção que pode armazenar qualquer tipo de objeto. Teremos uma classe simples chamada de Configuracao, contendo uma propriedade chamada Url. A instância desta classe será criada pelo WPF e estará armazenada estaticamente dentro dos recursos locais daquele formulário.

A sintaxe de binding agora consiste em configurar a propriedade Source com a instância criada e nomeada como "config". Continuamos a utilizar a propriedade Path, mas agora ela deverá refletir a propriedade do objeto que será preenchida pelo controle. Como podemos perceber, a propriedade Text do TextBox irá exibir a propriedade Url:

<Window x:Class="Teste.Window2"
    xmlns:local="clr-namespace:Teste">
    <Window.Resources>
        <local:Configuracao x:Key="config" />
    </Window.Resources>
    <Grid name="grid1">
        <TextBox Name="textBox1" Text="{Binding Source={StaticResource config}, Path=Url}" />
    </Grid>
</Window>

Suponhamos que temos vários controles e cada um receberá o valor de uma propriedade diferente. Ao invés de ficar repetindo a instância do objeto (config), podemos utilizar a propriedade DataContext. Essa propriedade nos permite compartilhar a mesma fonte por vários controles, e cada controle que o utilizará, apenas deve indicar qual propriedade ele estará vinculado. Repare que vinculamos o config à propriedade DataContext do controle Grid, e os controles inerentes à eles apenas mencionam qual propriedade cada um deles quer utilizar, sem a necessidade de especificar a propriedade Source.

<Window x:Class="Teste.Window2"
    xmlns:local="clr-namespace:Teste">
    <Window.Resources>
        <local:Configuracao x:Key="config" />
    </Window.Resources>
    <Grid name="grid1" DataContext="{StaticResource config}">
        <TextBox Name="textBox1" Text="{Binding Path=Url}" />
        <TextBox Name="textBox2" Text="{Binding Path=Timeout}"/>
    </Grid>
</Window>

Um detalhe importante aqui, é que se um controle não especificar nenhuma das propriedades de Binding (Source, RelativeSource ou Element), o WPF procura por algum elemento que possui a propriedade DataContext definida na árvore visual do formulário, e encontrando-o, tentará extrair o valor dele. Essa propriedade também pode ser configurada de via código, caso a instância do objeto precise de alguma manipulação adicional antes de ser usada pelo WPF.

this.grid1.DataContext = new Configuracao();

ObjectDataProvider

Como vimos acima, podemos criar a instância do objeto via código. Geralmente recorremos a essa técnica quando precisamos customizar a criação deste objeto. Mas o WPF fornece uma opção, que nos permite customizar de forma declarativa, algumas opções que podem ser utilizadas durante a criação deste objeto.

Entre essas opções, podemos definir alguns parâmetros para um construtor, fazer o databinding através de um método que retorna a instância do objeto, entre outras opções. Suponhamos que a partir de agora o construtor da classe Configuracao recebe como parâmetro uma string com a Url. Se tentar rodar a aplicação sem qualquer alteração, uma exceção será lançada dizendo que a classe não possui nenhum construtor público sem parâmetros. O ObjectDataProvider irá nos ajudar, permitindo especificar o valor deste parâmetro durante a criação:

<Window x:Class="Teste.Window2"
    xmlns:system="clr-namespace:System;assembly=mscorlib"
    xmlns:local="clr-namespace:Teste">
    <Window.Resources>
        <ObjectDataProvider x:Key="config" ObjectType="{x:Type local:Configuracao}">
            <ObjectDataProvider.ConstructorParameters>
                <system:String>http://wwww.israelaece.com</system:String>
            </ObjectDataProvider.ConstructorParameters>
        </ObjectDataProvider>
    </Window.Resources>
    <Grid DataContext="{StaticResource config}">
        <TextBox Name="textBox1" Text="{Binding Path=Url}" />
        <TextBox Name="textBox2" Text="{Binding Path=Timeout}"/>
    </Grid>
</Window>

RelativeSource

Já falamos das formas de databinding ElementName e Source, mas ainda nos resta uma terceira forma: a RelativeSource. Essa forma permite especificar como fonte, um objeto que está relativamente posicionado em relação ao objeto corrente. Essa opção traz três propriedades: Mode, AncestorType e AncestorLevel. A primeira delas recebe uma entre as seguintes opções:

  • FindAncestor: refere-se à um ancestral na árvore visual, que está acima em relação ao controle corrente. Essa opção exige que as propriedades AncestorType e AncestorLevel sejam definidas. A propriedade AncestorType define o tipo de elemento que será procurado, enquanto a propriedade AncestorLevel determina o nível de profundidade da busca.
  • PreviousData: permite referenciar o item anterior de uma coleção que está sendo exibida. Útil em templates.
  • Self: permite referenciar qualquer propriedade do objeto corrente.
  • TemplatedParent: permite referenciar um objeto que está envolvido em uma template.

O exemplo abaixo ilustra o uso do FindAncestor. Note que especificamos através do AncestorType o tipo de controle que desejamos procurar e o AncestorLevel, um número inteiro que determina até quantos níveis acima a busca será realizada. Neste caso, estamos interessados em exibir como o texto do botão o valor da propriedade Name de um Grid, e como estamos definindo o nível 2, ele irá apresentar o valor "g1" no botão.

<Window>
    <Grid Name="g1">
        <Grid Name="g2">
            <Button Name="button1">
                <Button.Content>
                    <Binding Path="Name">
                        <Binding.RelativeSource>
                            <RelativeSource Mode="FindAncestor" AncestorType="Grid" AncestorLevel="2" />
                        </Binding.RelativeSource>
                    </Binding>
                </Button.Content>
            </Button>
        </Grid>
    </Grid>
</Window>

Binding.Mode e Binding.UpdateSourceTrigger

Vimos até agora como podemos efetuar a ligação entre a origem e o destino das informações, mas não se resume a isso. Uma das característica do databinding permite também customizar que possíveis alterações sejam efetuadas para refletí-las tanto na origem quanto no destino. A propriedade Mode da classe Binding nos permite configurar como responder à essas ações, onde devemos escolher uma entre as cinco opções expostas pelo enumerador BindingMode:

  • Default: especifica que o binding utilizará o modo padrão estipulado pelo destino.
  • OneTime: especifica que o binding deve atualizar o destino quando a aplicação inicia ou quando os dados mudam, mas não deve atualizar o alvo quando subsequentes alterações são feitas na origem.
  • OneWay: especifica que o binding atualizará o destino quando a origem mudar. Alterações no destino não terão efeito na origem.
  • OneWayToSource: especifica que o binding atualizará a origem quando o destino mudar. Alterações na origem não terão efeito no destino.
  • TwoWay: especifica que as alterações feitas tanto na origem quanto no destino serão atualizadas automaticamente.

Outra propriedade que também é exposta pela classe Binding é a UpdateSourceTrigger, e que é utilizada quando a propriedade Mode é definida como OneWayToSource ou TwoWay. Essa propriedade determina como e quando a atualização das informações será realizada. Essa propriedade também receberá a informação oriunda de um enumerador, chamado UpdateSourceTrigger, onde as possíveis opções são:

  • Default: indica que a atualização será de acordo com o valor definido pela propriedade de destino, que muitas vezes é PropertyChanged. Propriedades que são editáveis pelo usuário, como a propriedade Text do TextBox, define o padrão como sendo LostFocus.
  • PropertyChanged: a fonte é atualizada quando a propriedade do destino é alterada.
  • LostFocus: a fonte é atualizada quando a propriedade de destino é alterada e quando o objeto perde o foco.
  • Explicit: a atualização da fonte será realizada quando você invocar explicitamente o método UpdateSource da classe Binding.

Coleções

Muitas vezes temos coleções de objetos que desejamos exibir através de controles, como por exemplo, ListBox. Da mesma forma que vimos anteriormente, para coleções, podemos proceder de forma semelhante, mas por se tratar de coleções, temos algumas novas propriedades que são exclusivas para esse cenário.

Controles que são considerados databound, expõe as seguintes propriedades DisplayMemberPath, ItemsSource e ItemTemplate. A primeira delas define o nome da propriedade do objeto que será exibida. Já a segunda representa a coleção que contém os itens que serão definidos com fonte das informações. Finalmente, a propriedade ItemTemplate, como o próprio nome diz, nos permite criar uma forma diferenciada para exibição de cada item da coleção. Dado uma coleção de clientes, onde cada elemento é representado por uma instância da classe Cliente, podemos fazer o seguinte:

<Window x:Class="Teste.Window3"
    xmlns:local="clr-namespace:Teste">
    <Window.Resources>
        <local:ColecaoDeClientes x:Key="cc" />
    </Window.Resources>
    <Grid>
        <ListBox
            Name="listBox1"
            ItemsSource="{Binding Source={StaticResource cc}}"
            DisplayMemberPath="Nome" />
    </Grid>
</Window>

O binding de coleções não está restrito à controles databound. Você pode também vincular uma coleção à um controle do tipo Label, mas como já era de se esperar, apenas o primeiro elemento será exibido. Para que se consiga navegar pelos elementos da coleção, você precisa recorrer à um mecanismo exposto pelo WPF que permite essa navegação.

Quando uma coleção é utilizada através do databinding, o WPF cria nos bastidores um objeto que implementa a interface ICollectionView (namespace System.ComponentModel). Essa interface disponibiliza membros que permite gerenciar a navegação, ordenação e agrupamento das informações. Para extrair este objeto, podemos utilizar o seguinte código:

ColecaoDeClientes cc = new ColecaoDeClientes();
ICollectionView view = CollectionViewSource.GetDefaultView(cc);

Entre os vários membros expostos por essa interface, temos: MoveCurrentToNext, MoveCurrentToPrevious, CurrentItem, etc. Não há o que comentar sobre cada um deles, pois são autoexplicativos. De posse da instância deste navegador, podemos navegar pelos registros de forma simples, sem precisar manualmente armazenar e incrementar ou decrementar índices na medida que o usuário for solicitando a visualização de um novo registro.

Data Templates

Muitas vezes, a visualização padrão fornecida por um controle databound não nos atende, ou por questões visuais ou porque a informação precisa ser customizada/formatada para cada item. Felizmente o WPF separa a funcionalidade do controle da sua visualização, permitindo que se customize completamente a aparência, sem perder ou ter que reescrever a funcionalidade de iteração de elementos.

Como o próprio nome diz, as data templates permite customizar a aparência de um controle, configurando como queremos que ele seja exibido. Tradicionalmente o ListBox exibe cada item um abaixo do outro, sem qualquer customização. Mas e se quisermos que cada elemento seja exibido como um TextBox, e dentro da propriedade Text termos a propriedade Nome vinculada? Abaixo podemos atingir esse objetivo com as data templates, onde customizamos o ListBox e para cada item, exibimos o valor da propriedade Nome dentro da propriedade Text de um TextBox. Repare que neste caso não utilizamos a propriedade DisplayMemberPath do ListBox, pois isso foi delegado ao template, para que ele determine onde e como mostrará o valor. Para quem já trabalhou com ASP.NET, mais precisamente com os controles DataList e Repeater, notará uma grande semelhança aqui.

<Window x:Class="Teste.Window3"
    xmlns:local="clr-namespace:Teste">
    <Window.Resources>
        <local:ColecaoDeClientes x:Key="cc" />
    </Window.Resources>
    <Grid>
        <ListBox Name="listBox1" ItemsSource="{Binding Source={StaticResource cc}}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBox Text="{Binding Path=Nome}" />
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Window>

ADO.NET

Para preencher os controles do formulário com dados de um banco de dados, você pode recorrer as mesmas técnicas apresentadas aqui. DataSets possuem uma forma tranquila para uso em databinding, mesmo no WPF. Você pode utilizar a propriedade DataContext que vimos acima, definindo para ela a instância do DataSet/DataTable com os dados carregados de um banco de dados qualquer, e a partir daí, utiliza-se a mesma sintaxe para exibição dessas informações no formulário.

Já quando trabalhamos com tecnologias mais recentes, como o LINQ To SQL ou Entity Framework, eles sempre geram coleções, e estas estariam vinculadas aos controles que fossem exibí-las.

Conclusão: Este artigo demonstrou superficialmente todo o poder do databinding do WPF. Vimos como podemos utilizar as mais variadas formas de preencher um controle, não somente com dados de um banco de dados, mas também com informações que são geradas por outros controles ou até mesmo por outros objetos. Esse novo modelo de trabalho facilita bastante a forma como efetuamos a ligação das informações, algo que é um pouco mais complicado em seu principal concorrente, o Windows Forms.

Tags: ,

Data | WPF

Powered by BlogEngine.NET 1.5.0.0
Theme by Mads Kristensen

Sobre

Meu nome é Israel Aece e sou especialista em tecnologias de desenvolvimento Microsoft, atuando como desenvolvedor de aplicações para o mercado financeiro utilizando a plataforma .NET. [ Mais ]

Twitter

Host