Desenvolvedores.Net - TechBlog

Category Archives: Desenvolvimento

LSP Liskov Subtitution Principle (Princípio da Substituição de Liskov)

0
1 Estrela2 Estrelas3 Estrelas4 Estrelas5 Estrelas (Sem votação.)
Loading...
14 de agosto de 2017

Veja o índice completo do tópico “S.O.L.I.D”

Olá pessoas …

Continuando a nossa saga de artigos, vamos agora ver a letra do acróstico que define a sigla LSP Liskov Substitution Principle (Princípio da Substituição de Liskov).

Definido o LSP

Não tem como falar deste princípio sem saber quem o criou, Barbara Liskov, em 1988.

A definição mais usada, e a mais simples, é:

note-taking Classes derivadas podem ser substituídas por suas classes de base.

Se q(x) é uma propriedade demonstrável dos objetos x de tipo T. Então q(y) deve ser verdadeiro para objetos y de tipo S onde S é um subtipo de T.

Portanto, a visão de “subtipo” defendida por Liskov é baseada na noção da substituição; isto é, se S é um subtipo de T, então os objetos do tipo T, em um programa, podem ser substituídos pelos objetos de tipo S sem que seja necessário alterar as propriedades deste programa.

(fonte: Wikipédia)

 

certo-ou-errado

Certo ou errado?

E lá vamos nós novamente ver como fica o princípio ferido, os problemas que nos trazem e a solução do mesmo …

 Ferindo o princípio

errado

Isto está errado.

Utilitário de atualização de saldos.

/*
 * Classe apenas para fins de exemplo e aprendizado não considera nenhum tipo de validação ou regra ou se utiliza de algum framework.
 */

using Solid.Errado.LSP.Abstract;

namespace Solid.Errado.LSP
{
    public static class AtualizarSaldo
    {
        #region Public Methods

        public static void AtualizarSaldos(params AtualizaSaldoBase[] saldos)
        {
            foreach(var saldo in saldos)
            {
                if(saldo is AtualizarSaldoAnual)
                    ((AtualizarSaldoAnual)saldo).AtualizarAno();
                else if(saldo is AtualizarSaldoMensal)
                    ((AtualizarSaldoMensal)saldo).AtualizarMes();
                else if(saldo is AtualizarSaldoDiario)
                    ((AtualizarSaldoDiario)saldo).AtualizarDia();
            }
        }

        #endregion Public Methods
    }
}

Abstração para as classes de saldo concretas

/*
 * Classe apenas para fins de exemplo e aprendizado não considera nenhum tipo de validação ou regra ou se utiliza de algum framework.
 */

namespace Solid.Errado.LSP.Abstract
{
    public abstract class AtualizaSaldoBase
    {
        #region Public Properties

        public double SaldoAtual { get; set; }

        #endregion Public Properties
    }
}

Cálculo de saldo diário

/*
 * Classe apenas para fins de exemplo e aprendizado não considera nenhum tipo de validação ou regra ou se utiliza de algum framework.
 */

using Solid.Errado.LSP.Abstract;
using System;

namespace Solid.Errado.LSP
{
    public class AtualizarSaldoDiario: AtualizaSaldoBase
    {
        #region Public Methods

        public void AtualizarDia()
        {
            Console.WriteLine("O saldo DIARIO foi atualizado.");
        }

        #endregion Public Methods
    }
}

Cálculo de saldo mensal

/*
 * Classe apenas para fins de exemplo e aprendizado não considera nenhum tipo de validação ou regra ou se utiliza de algum framework.
 */

using Solid.Errado.LSP.Abstract;
using System;

namespace Solid.Errado.LSP
{
    public class AtualizarSaldoMensal: AtualizaSaldoBase
    {
        #region Public Methods

        public void AtualizarMes()
        {
            Console.WriteLine("O saldo MENSAL foi atualizado.");
        }

        #endregion Public Methods
    }
}

Cálculo de saldo anual

/*
 * Classe apenas para fins de exemplo e aprendizado não considera nenhum tipo de validação ou regra ou se utiliza de algum framework.
 */

using Solid.Errado.LSP.Abstract;
using System;

namespace Solid.Errado.LSP
{
    public class AtualizarSaldoAnual: AtualizaSaldoBase
    {
        #region Public Methods

        public void AtualizarAno()
        {
            Console.WriteLine("O saldo ANUAL foi atualizado.");
        }

        #endregion Public Methods
    }
}

Problemas ao ferir o princípio

  • Primeiro, já matamos o polimorfismo. Na classe de atualização de saldos “public static class AtualizarSaldo” no método “public static void AtualizarSaldos(params AtualizaSaldoBase[] saldos)” temos que fazer “if/else” para determinar que tipo de classe iremos usar para calcular o saldo.
  • Na mesma classe citada acima, mesmo que utilizado a herança nas demais classes de atualização de saldo, eu não consigo o reaproveitamento do código de atualização de saldos, uma vez que eu tenho que identificar qual o tipo de classe e determinar o método que é chamado, veja no fragmento abaixo o “if/else”.
if(saldo is AtualizarSaldoAnual)
    ((AtualizarSaldoAnual)saldo).AtualizarAno();
else if(saldo is AtualizarSaldoMensal)
    ((AtualizarSaldoMensal)saldo).AtualizarMes();
else if(saldo is AtualizarSaldoDiario)
    ((AtualizarSaldoDiario)saldo).AtualizarDia();
  • Violação do princípio OCP
  • Dar manutenção neste código é ter dores de cabeça, uma vez que teremos que lembrar de alterar este método de cálculo de saldos sempre que um novo tipo de saldo for criado.

Corrigindo o princípio

Como o LSP anda de mãos dadas com o OCP, podemos perceber que as classes de saldos definidos no princípio OCP atendem 100% os requisitos deste princípio . Vamos relembrar estas classes:

certo

Isto está correto.

Veja as classes que foram criadas:

Abstração para os saldos, desta forma eu posso herdar minha classe principal e modificar apenas  o que eu preciso.

/*
 * Classe apenas para fins de exemplo e aprendizado não considera nenhum tipo de validação ou regra ou se utiliza de algum framework.
 */

namespace Solid.Certo.Utility.Abstract
{
    public abstract class AtualizaSaldoBase
    {
        #region Public Methods

        public abstract void Atualizar();

        #endregion Public Methods
    }
}

Classe concreta de atualização de saldos diários.

/*
 * Classe apenas para fins de exemplo e aprendizado não considera nenhum tipo de validação ou regra ou se utiliza de algum framework.
 */

using Solid.Certo.Utility.Abstract;
using System;

namespace Solid.Certo.Utility
{
    public class AtualizaSaldoDiario: AtualizaSaldoBase
    {
        #region Public Methods

        public override void Atualizar()
        {
            Console.WriteLine("O saldo DIÁRIO foi atualizado.");
        }

        #endregion Public Methods
    }
}

Classe concreta de atualização de saldos mensais.

/*
 * Classe apenas para fins de exemplo e aprendizado não considera nenhum tipo de validação ou regra ou se utiliza de algum framework.
 */

using Solid.Certo.Utility.Abstract;
using System;

namespace Solid.Certo.Utility
{
    public class AtualizaSaldoMensal: AtualizaSaldoBase
    {
        #region Public Methods

        public override void Atualizar()
        {
            Console.WriteLine("O saldo MENSAL foi atualizado.");
        }

        #endregion Public Methods
    }
}

Classe concreta de atualização de saldos anuais.

/*
 * Classe apenas para fins de exemplo e aprendizado não considera nenhum tipo de validação ou regra ou se utiliza de algum framework.
 */

using Solid.Certo.Utility.Abstract;
using System;

namespace Solid.Certo.Utility
{
    public class AtualizaSaldoAnual: AtualizaSaldoBase
    {
        #region Public Methods

        public override void Atualizar()
        {
            Console.WriteLine("O saldo ANUAL foi atualizado.");
        }

        #endregion Public Methods
    }
}

Classe de serviço para atualização de saldos no estoque.

/*
 * Classe apenas para fins de exemplo e aprendizado não considera nenhum tipo de validação ou regra ou se utiliza de algum framework.
 */

using Solid.Certo.Utility.Abstract;
using System.Collections.Generic;

namespace Solid.Certo.Service
{
    public static class AtualizaSaldoService
    {
        #region Public Methods

        public static void AtualizarSaldos(List<AtualizaSaldoBase> saldos)
        {
            foreach(AtualizaSaldoBase saldo in saldos)
            {
                saldo.Atualizar();
            }
        }

        #endregion Public Methods
    }
}

Analisando as classes definidas acima, podemos perceber que:

  • As classes de saldo herdam diretamente da classe “public abstract class AtualizaSaldoBase”;
  • Passamos a ter acesso ao método “Atualizar()”, desta forma eu consigo chamar o método “Atualizar()” a partir da classe mais genérica (Veja: Generalização e Especialização), é utilizado o polimorfismo para evitar o uso de “if/ else”, como podemos ver no fragmento abaixo da classe de serviço de atualização de saldo.
public static class AtualizaSaldoService
{
	#region Public Methods

	public static void AtualizarSaldos(List<AtualizaSaldoBase> saldos)
	{
		foreach(AtualizaSaldoBase saldo in saldos)
		{
			saldo.Atualizar();
		}
	}

	#endregion Public Methods
}
  • Se necessário criar um novo tipo de saldo, basta criar a classe, herdar de “AtualizaSaldoBase” e implementar a chamada onde queremos que o novo saldo seja calculado sem termos que nos preocupar em fazer “if” para identificar o tipo de  saldo que iremos calcular;
  • A classe mais especializada pode ser facilmente convertida na classe mais genérica e atende ao princípio em questão;

Pegadinha e cuidados

Que diabos O patinho feio; história infantil; está fazendo aqui?

Ora! É simples. Se nada como um pato, tem as habilidades de um pato, come igual ao pato mas é um marreco cisne, então você tem um problema de abstração.

Vamos ao exemplo mais clássico de erro de abstração Quadrado x Retângulo. Não poderíamos falar de LSP sem comentar sobre o Quadrado x Retângulo.

Todo quadrado é também um retângulo, pois ambos tem ângulos retos. Mas nem todo retângulo é um quadrado. Quem me disse foi o Professor Procópio do Matemática Rio, no vídeo Qual a Diferença entre Retângulo e Quadrado?

Vamos aos fontes:

Classe de retângulo.

/*
 * Classe apenas para fins de exemplo e aprendizado não considera nenhum tipo de validação ou regra ou se utiliza de algum framework.
 */

namespace Solid.Errado.QuadradoRetangulo
{
    public class Retangulo
    {
        #region Public Properties

        public virtual int Altura { get; set; }
        public virtual int Largura { get; set; }

        #endregion Public Properties
    }
}

Classe do quadrado que herda de retângulo (Já falei sobre herança?)

/*
 * Classe apenas para fins de exemplo e aprendizado não considera nenhum tipo de validação ou regra ou se utiliza de algum framework.
 */

namespace Solid.Errado.QuadradoRetangulo
{
    public class Quadrado: Retangulo
    {
    }
}

Utilitário para cálculo de área.

/*
 * Classe apenas para fins de exemplo e aprendizado não considera nenhum tipo de validação ou regra ou se utiliza de algum framework.
 */

using Solid.Errado.QuadradoRetangulo;

namespace Solid.Errado.Utility
{
    public static class CalculaArea
    {
        #region Public Methods

        //Sim, este método poderia ser da classe retângulo.
        //Mas para efeito de exemplo, vou deixar ele aqui.
        public static int CalcularArea(Retangulo retangulo)
        {
            return retangulo.Altura * retangulo.Largura;
        }

        #endregion Public Methods
    }
}

Chamada de exemplo para ambos os casos

int altura;
int largura;

Console.WriteLine("\r\nInforme a altura:");
altura = Convert.ToInt32(Console.ReadLine());
Console.WriteLine("Informe a largura:");
largura = Convert.ToInt32(Console.ReadLine());

//aqui, podemos criar um quadrado normalmente.
Quadrado quadrado = new Quadrado
{
    Altura = altura,
    Largura = largura
};

//aqui, criamos um retângulo com base em um quadrado, atendendo ao princípio do LSP
Retangulo retanguloQuadrado = new Quadrado
{
    Altura = altura,
    Largura = largura
};

//Aqui criamos um retângulo normalmente.
Retangulo retangulo = new Retangulo
{
    Altura = altura,
    Largura = largura
};

int quadradoAreaEsperada = altura * altura;
int retanguloAreaEsperada = largura * altura;
//o método CalcularArea() espera um retângulo, perceba que podemos passar tanto um quadrado quanto um retângulo. Atendendo ao princípio LSP.
Console.WriteLine($"A área correta do retângulo é {retanguloAreaEsperada} e a calculada foi {Errado.Utility.CalculaArea.CalcularArea(retangulo)}");
Console.WriteLine($"A área correta do quadrado  é {quadradoAreaEsperada} e a calculada foi {Errado.Utility.CalculaArea.CalcularArea(quadrado)}");   

Vamos analisar os fontes e explicar o real problema de não tomarmos cuidado no momento de definirmos nossas abstrações.

As classes definidas, a primeira vista, não ferem o princípio LSP, uma vez que podem ser herdadas, terem seus métodos sobrescritos, são reutilizáveis e eu posso criar a mais genérica pela classe mais especializada, posso utilizar a classe mais genérica como parâmetro e passar a mais especializada, como diz o princípio da substituição.
No caso, como foi mostrado, eu posso criar um retângulo a partir de um quadrado. Veja o fragmento abaixo

//aqui, criamos um retângulo com base em um quadrado, atendendo ao princípio do LSP
Retangulo retanguloQuadrado = new Quadrado
{
    Altura = altura,
    Largura = largura
};

Mas isso, tem um erro. Imagina que nossos sistema é usado para calcular as esquadrias em um prédio. E neste sistema eu preciso calcular a área para pedir que se corte uma esquadria quadrada. considerando que o quadrado tem seus lados iguais, e meu sistema aceita que eu passe altura x largura diferentes, mesmo para a classe “Quadrado”, eu tenho um erro de regra de negócio e de abstração.

Se eu crio uma classe retângulo a partir de uma classe “Quadrado” a área calculada ficará errada se eu passar os valores de “Altura” e “Largura” diferentes.

//aqui, criamos um retângulo com base em um quadrado, atendendo ao princípio do LSP
Retangulo retanguloQuadrado = new Quadrado
{
    Altura = 10,
    Largura = 5
};

//aqui acontece o erro, uma vez que eu passei 10x5=50. O meu calculo de área deveria considerar Altura X Largura igual para o calculo de quadrados.
Console.WriteLine($"A área correta do quadrado  é {Errado.Utility.CalculaArea.CalcularArea(retanguloQuadrado)}");

Pegando um exemplo real, nos artigos anteriores temos tratado as nossas classes de saldos como saldos de estoque. Imaginem que eu queira calcular o saldo financeiro. Em sua essência o cálculo é igual um menos o outro, entradas menos saídas. Só que de um lado eu estou falando de produtos, e de outro de valores monetários, cada qual com suas regras específicas.

Para terminar

Para atender ao princípio de Liskov, temos que garantir que as nossa classes genéricas, sejam substituíveis pelas nossas classes especializadas; Veja: Generalização x Especialização; Desta forma atendemos também o OCP.

Cuidado ao usar o princípio “É UM”, e preste muita atenção que este princípio se aplica ao comportamento da classe e não aos tipos ou subtipos. Valorize o polimorfismo.

SaberMais

Para saber mais.

O fonte utilizado aqui pode ser baixado pelo GITHub em https://github.com/desenvolvedores-net/ArtigoSOLID


É isso ai pessoal 🙂
Até o próximo
♦ Marcelo

OCP Open/ Closed Principle (Princípio aberto/fechado)

0
1 Estrela2 Estrelas3 Estrelas4 Estrelas5 Estrelas (1 votos, média: 5,00 de 5)
Loading...
10 de agosto de 2017

Veja o índice completo do tópico “S.O.L.I.D”

Olá pessoas …

Continuando a nossa saga de artigos, vamos agora ver a letra O do acróstico, que define a sigla OCP Open/Closed Principle (Princípio aberto/ fechado).

Definindo OCP

 

 

Momento piadinha infame do artigo

Não iremos falar sobre o Robocop, mas toda vez que eu falo a sigla  OCP (Omni Consumer Products) eu vejo o Robocop na minha mente

 

 

 

Bom, vamos ao que interessa…

note-taking Você deve ser capaz de estender um comportamento de uma classe sem modificá-lo.

Esta declaração quer dizer que você tem que escrever suas classes de forma que elas possam ser herdadas e ter seu comportamento estendido ao invés de alterar o código existente. Para fazer isso, iremos usar o que chamamos de herança e abstração.

SaberMais Para saber mais.

Caso não esteja familiarizado com os termos herança e abstração, dê uma lida no artigo sobre “Herança”  e no artigo sobre “Abstração“.
Tenho certeza que suas dúvidas serão sanadas.

Problemas em ferir o princípio OCP

  • Reaproveitamento de código é praticamente nulo. CTRL+C/ CTRL+V, não é reaproveitamento de código;
  • As classes podem fazer partes de componentes espalhados pela aplicação;
  • O que acontece se precisarmos de uma nova implementação?
    • Colocamos mais um if/else if, um switch…case ?
    • Criamos uma nova classe e copiamos o código?
  • Recompilar e fazer deploy em todos os componentes impactados;

Espero que você tenha um bom caso de testes escrito para cada situação.

certo-ou-errado Certo ou errado?

Abaixo vou colocar os códigos “ERRADO” e “CERTO” e ao final, analisar cada um.

Ferindo o OCP

errado

Isto está errado.

Primeiro, vamos criar nossa classe de venda para atender as vendas da nossa aplicação.

/*
 * Classe apenas para fins de exemplo e aprendizado não considera nenhum tipo de validação ou regra ou se utiliza de algum framework.
 */

using System;
using System.Collections.Generic;

namespace Solid.Errado
{
    public class Venda
    {
        #region Public Properties

        public int Codigo { get; set; }
        public int CodigoCliente { get; set; }
        public List<string> Itens { get; set; }
        public double TotalVenda { get; set; }

        #endregion Public Properties

        #region Public Methods

        public void Salvar()
        {
            Validar();
            Console.WriteLine("A venda foi salva.");
            CalcularTotal();
            AtualizarSaldo();
        }

        private void CalcularTotal()
        {
            Console.WriteLine("O total da venda foi calculado.");
        }

        public void Validar()
        {
            if(CodigoCliente == 0)
                throw new Exception("O código do cliente é obrigatório.");

            if((Itens?.Count ?? 0) == 0)
                throw new Exception("A quantidade de itens vendida não pode ser zero.");
        }

        public void AtualizarSaldo()
        {
            Utility.AtualizarSaldo.AtualizarSaldos(new AtualizarSaldoAnual(),
                                                   new AtualizarSaldoMensal(),
                                                   new AtualizarSaldoDiario());
        }
        #endregion Public Methods
    }
}

Com a classe de vendas criada, precisamos atualizar os saldos de estoque, pois a venda faz uma saída em nossos produtos.
Esta classe é um utilitário de atualização de saldos de estoque, e recebe objetos para realizar os cálculos de saldo de estoque.

/*
 * Classe apenas para fins de exemplo e aprendizado não considera nenhum tipo de validação ou regra ou se utiliza de algum framework.
 */

 namespace Solid.Errado.Utility
{
    public static class AtualizarSaldo
    {
        #region Public Methods

        public static void AtualizarSaldos(params object[] saldos)
        {
            foreach(var saldo in saldos)
            {
                if(saldo.GetType() == typeof(AtualizarSaldoAnual))
                    ((AtualizarSaldoAnual)saldo).Atualizar();
                else if(saldo.GetType() == typeof(AtualizarSaldoMensal))
                    ((AtualizarSaldoMensal)saldo).Atualizar();
                else if(saldo.GetType() == typeof(AtualizarSaldoDiario))
                    ((AtualizarSaldoDiario)saldo).Atualizar();
            }
        }

        #endregion Public Methods
    }
}

E abaixo, as classes de atualização de saldo diário, mensal e anual.

Saldo Diário

/*
 * Classe apenas para fins de exemplo e aprendizado não considera nenhum tipo de validação ou regra ou se utiliza de algum framework.
 */

using System;

namespace Solid.Errado
{
    public class AtualizarSaldoDiario
    {
        #region Public Methods

        public void Atualizar()
        {
            Console.WriteLine("O saldo DIARIO foi atualizado.");
        }

        #endregion Public Methods
    }
}

Saldo Mensal

/*
 * Classe apenas para fins de exemplo e aprendizado não considera nenhum tipo de validação ou regra ou se utiliza de algum framework.
 */

using System;

namespace Solid.Errado
{
    public class AtualizarSaldoMensal
    {
        #region Public Methods

        public void Atualizar()
        {
            Console.WriteLine("O saldo MENSAL foi atualizado.");
        }

        #endregion Public Methods
    }
}

Saldo Anual

/*
 * Classe apenas para fins de exemplo e aprendizado não considera nenhum tipo de validação ou regra ou se utiliza de algum framework.
 */

using System;

namespace Solid.Errado
{
    public class AtualizarSaldoAnual
    {
        #region Public Methods

        public void Atualizar()
        {
            Console.WriteLine("O saldo ANUAL foi atualizado.");
        }

        #endregion Public Methods
    }
}

Logo de cara já percebemos que a classe de vendas fere completamente o Princípio da Responsabilidade Única, ela se “valida”, ele atualiza os saldos de estoque entre outros problemas que você já é capaz de identificar com a leitura do primeiro artigo.

Mas o que realmente nos interessa aqui, são as classes de saldos. Estas ferem completamente o princípio OCP (Aberto/ Fechado).

  • Mesmo que eu faça a herança destas classes, por exemplo, para criar um saldo quinzenal, eu não consigo substituir o comportamento do método atualizar;
  • O utilitário de atualização de saldos, classe “Utility.AtualizarSaldo“, tem que conhecer as classes de saldos e fazer um “if” para identificar qual é o tipo de saldo que deverá ser atualizado;
    • Se eu precisar implementar mais um tipo de saldo, tenho que lembrar deste “if” e criar um método com o mesmo nome “Atualizar” para manter um mínimo de padrão;
    • Este utilitário aceita tipos “object“, isso quer dizer que se eu passar uma classe errada para o meu método, posso ter um erro em tempo de execução, ou não ter meus saldos de estoque calculados corretamente;
  • Percebemos que não houve reaproveitamento de código entre as classes de saldos;

Corrigindo o princípio

Neste momento, iremos corrigir o princípio e ao final analisar as modificações que foram realizadas para atender ao princípio do OCP.

certo

Isto está correto.

Veja as classes que foram criadas:

Classe de venda para realização das vendas em nosso estabelecimento.

/*
 * Classe apenas para fins de exemplo e aprendizado não considera nenhum tipo de validação ou regra ou se utiliza de algum framework.
 */

using Solid.Certo.Persistence.Abstract;
using Solid.Certo.Repository;
using System.Collections.Generic;

namespace Solid.Certo.Persistence
{
    public class Venda: EntityBase
    {
        #region Public Properties

        public int CodigoCliente { get; set; }
        public List<string> Itens { get; set; }
        public double TotalVenda { get; set; }

        #endregion Public Properties

        #region Public Methods

        public override void Salvar()
        {
            DBContext<Venda>.Salvar(this);
        }

        #endregion Public Methods
    }
}

Abstração para os saldos, desta forma eu posso herdar minha classe principal e modificar apenas  o que eu preciso.

/*
 * Classe apenas para fins de exemplo e aprendizado não considera nenhum tipo de validação ou regra ou se utiliza de algum framework.
 */

namespace Solid.Certo.Utility.Abstract
{
    public abstract class AtualizaSaldoBase
    {
        #region Public Methods

        public abstract void Atualizar();

        #endregion Public Methods
    }
}

Classe concreta de atualização de saldos diários.

/*
 * Classe apenas para fins de exemplo e aprendizado não considera nenhum tipo de validação ou regra ou se utiliza de algum framework.
 */

using Solid.Certo.Utility.Abstract;
using System;

namespace Solid.Certo.Utility
{
    public class AtualizaSaldoDiario: AtualizaSaldoBase
    {
        #region Public Methods

        public override void Atualizar()
        {
            Console.WriteLine("O saldo DIÁRIO foi atualizado.");
        }

        #endregion Public Methods
    }
}

Classe concreta de atualização de saldos mensais.

/*
 * Classe apenas para fins de exemplo e aprendizado não considera nenhum tipo de validação ou regra ou se utiliza de algum framework.
 */

using Solid.Certo.Utility.Abstract;
using System;

namespace Solid.Certo.Utility
{
    public class AtualizaSaldoMensal: AtualizaSaldoBase
    {
        #region Public Methods

        public override void Atualizar()
        {
            Console.WriteLine("O saldo MENSAL foi atualizado.");
        }

        #endregion Public Methods
    }
}

Classe concreta de atualização de saldos anuais.

/*
 * Classe apenas para fins de exemplo e aprendizado não considera nenhum tipo de validação ou regra ou se utiliza de algum framework.
 */

using Solid.Certo.Utility.Abstract;
using System;

namespace Solid.Certo.Utility
{
    public class AtualizaSaldoAnual: AtualizaSaldoBase
    {
        #region Public Methods

        public override void Atualizar()
        {
            Console.WriteLine("O saldo ANUAL foi atualizado.");
        }

        #endregion Public Methods
    }
}

Estratégia de venda, responsável pela nossa regra de negócio quando a venda for realizada.

/*
 * Classe apenas para fins de exemplo e aprendizado não considera nenhum tipo de validação ou regra ou se utiliza de algum framework.
 */

using Solid.Certo.Persistence;
using Solid.Certo.Strategy.Abstract;
using Solid.Certo.Utility.Abstract;
using System;
using System.Collections.Generic;

namespace Solid.Certo.Strategy
{
    public class VendaStrategy: PersistenceStrategyBase<Venda>
    {
        #region Public Methods

        public override void AntesDeSalvar(Venda entity)
        {
            base.AntesDeSalvar(entity);
            //vamos simular uma cálculo de total de venda
            Entidade.TotalVenda = 30;
            Console.WriteLine("O total da venda foi calculado");
        }

        public override IEnumerable<string> Validar()
        {
            if(Entidade.CodigoCliente == 0)
                yield return "O código do cliente é obrigatório.";

            if((Entidade.Itens?.Count ?? 0) == 0)
                yield return "A quantidade de itens vendida não pode ser zero.";

            yield break;
        }

        public override void DepoisDeSalvar(Venda entity)
        {
            base.DepoisDeSalvar(entity);

            Service.AtualizaSaldoService.AtualizarSaldos(new List<AtualizaSaldoBase>
            {
                new Utility.AtualizaSaldoDiario(),
                new Utility.AtualizaSaldoMensal(),
                new Utility.AtualizaSaldoAnual()
            });
        }

        #endregion Public Methods
    }
}

Classe de serviço para atualização de saldos no estoque.

/*
 * Classe apenas para fins de exemplo e aprendizado não considera nenhum tipo de validação ou regra ou se utiliza de algum framework.
 */

using Solid.Certo.Utility.Abstract;
using System.Collections.Generic;

namespace Solid.Certo.Service
{
    public static class AtualizaSaldoService
    {
        #region Public Methods

        public static void AtualizarSaldos(List<AtualizaSaldoBase> saldos)
        {
            foreach(AtualizaSaldoBase saldo in saldos)
            {
                saldo.Atualizar();
            }
        }

        #endregion Public Methods
    }
}

Como podemos ver, até a classe de vendas foi corrigida. Não fere mais  o primeiro princípio.

Como o foco foram as classes de saldos, iremos listar as vantagens desta abordagem.

  • Com o uso da abstração; (clique para saber mais sobre Abstração); conseguimos isolar os métodos comuns das nossas classes de cálculo de saldos;
  • Fizemos com que as classes filhas, implementassem o método “Atualizar” para que cada uma saiba como fazer a atualização de saldos em estoque;
  • As estratégias indicam o momento em que a atualização dos saldos devem ocorrer;
  • Se for necessário a inclusão do saldo quinzenal, basta apenas criar a classe e inserir na estratégia o momento em que se deseja atualizar o saldo;
  • Não é necessário fazer o uso de “if/else if” ou “switch…case” no momento da atualização de saldos, isso foi resolvido com o Polimorfismo;
    • Perceba que ao utilizar a abstração para chamar a atualização de  saldos, nos não precisamos mais nos preocupar em fazer “if/else if” e nem se preocupar se o tipo de objeto passado é do tipo saldo. Uma vez que a herança; (clique para saber mais sobre Herança) me permite fazer isto de forma transparente e garante que o tipo passado é do tipo saldo;
  • E se necessário fazer uma customização para determinado cliente, podemos usar e abusar do padrão “Strategy“.

O fonte utilizado aqui pode ser baixado pelo GITHub em https://github.com/desenvolvedores-net/ArtigoSOLID


É isso ai pessoal 🙂
Até o próximo
♦ Marcelo

SRP Single Responsibility Principle (Princípio da Responsabilidade Única)

1
1 Estrela2 Estrelas3 Estrelas4 Estrelas5 Estrelas (1 votos, média: 5,00 de 5)
Loading...
9 de agosto de 2017


Veja o índice completo do tópico “S.O.L.I.D”

Olá pessoas …

Dando início a nossa saga de artigos, vamos iniciar com a primeira letra do acróstico, S que define a sigla SRP Single Responsibility Principle (Princípio da Responsabilidade Única).

Definição

Isto quer dizer que nossas classes devem ter apenas uma, e somente uma, razão para ser modificada. Se a classe possuir mais de um motivo para ser modificada, a mesma não é coesa e isso já fere os princípios da POO (Programação Orientada à Objetos).

Problemas ao ferir o primeiro princípio.

  • Dificuldade de compreensão, logo dificuldade de manutenção na classe;
  • Não permite reuso;
  • Alto acoplamento, a classe depende de conhecer outras e outras e mais outras classes para poder funcionar, o que dificulta a manutenção ao alterar as classes de dependência;

Imaginem um canivete suíço, para quem não sabe, é este ai abaixo, o pai dos canivetes suíços.

Canivete criado por John S. Holler por volta de 1880, na Alemanha e possui 100 funções.
As tampinhas só foram inventadas em 1891. 
Fonte: Google

Este canivete, se fosse uma aplicação, fere completamente o primeiro princípio. O da responsabilidade única. Em nossas aplicações cada objeto deve fazer apenas o que ele se propõe a fazer.

A ostra ostreia, o gato gateia, o vento venta
(Prof. Clóvis Barros Filho)

Nossas classes devem seguir esta linha de pensamento, se é uma classe de serialização, ela serializa, seja em banco de dados, arquivos, memória. Não importa, ela apenas serializa.
Se nossa classe é de validação, ela apenas valida.

Certo ou Errado?

Nos exemplos abaixo eu sempre colocarei o certo e o errado para compararmos as classes e as soluções propostas. Tentarei usar problemas e soluções do dia-a-dia.

Antes de começarmos vamos ver a imagem abaixo:

Como podemos ver, é muito mais simples criar da forma errada. Mas te trará problemas de manutenção de código, de reusabilidade de código, seu código será macarrônico e anêmico. Alto Acoplamento e baixa coesão. No momento da manutenção deste código, erros simples se tornam complexos de serem resolvidos.

Como ferir o princípio.

Veja o exemplo de código abaixo:

errado

Isto está errado.

/*
 * Classe apenas para fins de exemplo e aprendizado não considera nenhum tipo de validação ou regra ou se utiliza de algum framework.
 */

 using System;
using System.Collections.Generic;

namespace Solid.Errado
{
    public class Cliente
    {
        #region Private Methods

        private void Adicionar()
        {
            Validar();
            Console.WriteLine($"O cliente {Nome} foi inserido.");
        }

        private void Atualizar()
        {
            Validar();
            Console.WriteLine($"O cliente {Nome} foi atualizado.");
        }

        #endregion Private Methods

        #region Public Properties

        public string CNPJ { get; set; }
        public int Codigo { get; set; }
        public DateTime DataCadastro { get; set; }
        public string Email { get; set; }
        public string Endereco { get; set; }
        public string Nome { get; set; }

        #endregion Public Properties

        #region Public Methods

        public void EnviarEmail()
        {
            Console.WriteLine($"O e-mail foi enviado para o endereço {Email}");
        }

        public void Excluir(long id)
        {
            Console.WriteLine($"O cliente {Nome} foi excluído.");
        }

        public void Salvar()
        {
            if(Codigo != 0)
                Atualizar();
            else
                Adicionar();

            EnviarEmail();
        }

        public IList<Cliente> Selecionar(long? id = null)
        {
            List<Cliente> result = new List<Cliente>();

            return result;
        }

        public bool Validar()
        {
            if(String.IsNullOrWhiteSpace(Nome))
                throw new Exception("O nome é obrigatório");

            if(!Email.Contains("@"))
                throw new Exception("O e-mail não é válido");

            if(CNPJ.Length != 14)
                throw new Exception("O CNPJ não é válido");

            return true;
        }

        #endregion Public Methods
    }
}

A classe cliente escrita acima, tem diversos pontos que ferem o princípio da Responsabilidade Única:

  • Ela é uma classe que se faz sua própria serialização, como pode ver na linha 17 do método “Adicionar()”, linha 23 do método “Atualizar()” e assim nos demais métodos. Logo esta classe tem que conhecer a implementação de sua forma de serialização, seja banco de dados, XML, JSON, memória;
  • Esta classe é responsável por enviar um e-mail ao cliente, assim que o mesmo é cadastrado na aplicação. Imagina se você criar outras classes de pessoas, como Fornecedor, Usuário, Fabricante, e se fizer necessário o envio de e-mail ao se criar cada pessoa. Cada classe terá sua implementação do método “EnviarEmail()” e você terá que dar manutenção em cada método das classes de pessoas, se por um acaso o servidor for modificado, por exemplo;
  • Esta classe é  responsável por validar seus próprios dados. Em uma customização para clientes, esta abordagem seria trabalhosa, uma vez que cada cliente pode ter sua forma de validação de dados;

Como resolver o problema.

certo

Isto está correto.

/*
 * Classe apenas para fins de exemplo e aprendizado não considera nenhum tipo de validação ou regra ou se utiliza de algum framework.
 */

using Solid.Certo.Persistence.Abstract;
using Solid.Certo.Repository;
using System;

namespace Solid.Certo.Persistence
{
    public class Cliente: EntityBase
    {
        #region Public Properties

        public string CNPJ { get; set; }
        public DateTime DataCadastro { get; set; }
        public string Email { get; set; }
        public string Endereco { get; set; }
        public string Nome { get; set; }

        #endregion Public Properties

        #region Public Methods

        public void Excluir()
        {
            DBContext<Cliente>.Excluir(Codigo);
        }

        public void Salvar()
        {
            DBContext<Cliente>.Salvar(this);
        }

        #endregion Public Methods
    }
}

Como podem ver:

  • Esta classe não se “Valida”;
    • Para resolver este problema utilizamos o padrão de projeto “Strategy“. Que são:
      • StrategyDependencyManager: Responsável por fazer a injeção de dependência das estratégias na classe cliente;
      • IStrategy: Define o contrato para qualquer estratégia que não seja de persistência;
      • IPersistenceStrategy: Define o contrato para as estratégias de persistência dos dados;
      • StrategyBase: Abstração de estratégias para os tipos mais básicos;
      • PersistenceStrategyBase: Abstração para estratégias de persistências;
      • ClienteStrategy: Estratégia concreta para validação de clientes;
    • A validação do CNPJ passou a ser de responsabilidade do padrão Validator (Visitor Pattern).
      • IValidator: Contrato para classes de validação de tipos;
      • CNPJValidator: Classe concreta para validação do tipo CNPJ;
        • Estes tópicos, Visitor Pattern e Validators serão discutidos em outros artigos. Aqui iremos falar apenas do conceito S.O.L.I.D.
  • Ela não conhece a implementação de serialização, no caso, podemos perceber que seria em um banco de dados;
    • Este problema foi resolvido usando a classe DbContext, mas temos que tomar cuidado com esta abordagem, pois depende do framework de acesso à dados que você vai utilizar, o mais comum, para desenvolvimento .NET é o Entity Framework. Este link do StackOverflow; em Português; tem uma boa explicação sobre com prós e contras;
  • Ela não manda e-mails;
    • Quem decide se o e-mail deve ou não ser enviado é a estratégia de cada cliente, para isso a estratégia utiliza-se do serviço de e-mail para o envio do e-mail para o cliente.
      • EmailService: Classe de serviço de e-mail responsável por enviar e-mail e receber e-mail em toda a aplicação;

Como podemos ver, esta classe apenas faz o que lhe diz respeito. Manter os dados dos clientes.

Não se preocupe com os nomes de classes e conceitos explicados acima como solução para a forma correta. Durante o desenvolvimento dos demais artigos, irei explicar todas as classes e os princípios aplicados.

O fonte utilizado aqui pode ser baixado pelo GITHub em https://github.com/desenvolvedores-net/ArtigoSOLID

 


É isso ai pessoal 🙂
Até o próximo
♦ Marcelo

S.O.L.I.D

2
1 Estrela2 Estrelas3 Estrelas4 Estrelas5 Estrelas (1 votos, média: 5,00 de 5)
Loading...
31 de julho de 2017

Use este post como um índice para os demais artigos, basta clicar nos links para ler os artigos.

Olá galera…

Estou aqui mais uma vez com uma série de artigos, desta vez irei falar sobre S.O.L.I.D. Sim, eu sei que existem centenas de artigos sobre o assunto por ai, mas minha intenção aqui é aprender um pouco mais sobre o assunto com alguns exemplos em C#.

Afinal, o que é S.O.L.I.D.?

O padrão S.O.L.I.D. são cinco princípios que nos ajudam a desenvolver aplicações mais robustas e de fácil manutenção. Vamos entender o que é cada um dos princípios nos tópicos abaixo.

Estes princípios foram definidos pelo Robert. C. Martin, ou se preferirem, Uncle Bob, e datam do início do ano 2000.

S.O.L.I.D. é um acróstico para os seguintes acrônimos :

Para ler em detalhes cada uma das siglas, basta clicar na sigla ou em sua definição.

SRP

Single Responsibility Principle (Princípio da Responsabilidade Única)

note-taking Uma classe deve ter um único, e somente um, motivo para que possa ser modificada.
A class should have one, and only one, reason to change.

OCP

Open/ Closed Principle (Princípio aberto/fechado)

note-taking Você deve ser capaz de estender um comportamento de uma classe sem modificá-lo.
You should be able to extend a classes behavior, without modifying it.

LSP

Liskov Subtitution Principle (Princípio da Substituição de Liskov)

note-taking As classes derivadas devem poder substituir suas classes bases.
Derived classes must be substitutable for their base classes.

ISP

Interface Segregation Principle (Princípio de segregação de interface)

note-taking Interfaces específicas são melhores do que uma interface geral
Make fine grained interfaces that are client specific.

DIP

Dependency Inversion Principle (Princípio da inversão de dependência)

note-taking Dependa de uma abstração e não de uma implementação.
Depend on abstractions, not on concretions.

Atenção

Durante o desenvolvimento dos exemplos, não vou usar nenhum ORM, Framework ou qualquer outra ferramenta de apoio para não confundir a nossa cabeça, irei apenas aplicar os conceitos de S.O.L.I.D nos exemplos.

Os fontes utilizados como exemplo podem ser baixados pelo GITHub em https://github.com/desenvolvedores-net/ArtigoSOLID

SaberMais Para saber mais.

 


É isso ai pessoal 🙂
Até o próximo
♦ Marcelo

Grupo de estudos Xamarin

0
1 Estrela2 Estrelas3 Estrelas4 Estrelas5 Estrelas (Sem votação.)
Loading...
1 de junho de 2017

Eae galera … Este é apenas um post rápido para divulgar o novo grupo de estudos no WhatsAPP. O Xamarin Brasil. Segue link

https://chat.whatsapp.com/Im35GeAi4Dg6lTAGFD4OqH

Ou se preferir, utilize o grupo.



É isso ai pessoal 🙂
Até o próximo
♦ Marcelo

About Marcelo

Nascido em Juruaia/MG em uma fazenda de criação de búfalos, e residindo na região Sul do Brasil. Trabalha com desenvolvimento de aplicações desde os 17 anos. Atualmente é Arquiteto Organizacional na Unimake Software. Para saber mais ... http://desenvolvedores.net/marcelo []'s

Delagates Func, Action e Predicate

0
1 Estrela2 Estrelas3 Estrelas4 Estrelas5 Estrelas (1 votos, média: 5,00 de 5)
Loading...
1 de julho de 2016

delegateFuncActionPredicate

Veja o índice completo do tópico “Delegate”

Olá 🙂

Nos artigos anteriores vimos o uso do delegates em eventos, para criar chamadas estilo callback, como invocar métodos e como criar seus delegates.

E continuando nosso assunto sobre delegates, iremos falar sobre três importantes delegates dentro do C#, Func, Action e Predicate.

Func & Action

Nos artigos anteriores criamos uma calculadora usando delegate, aproveitando aquele exemplo, iremos reescrever nossa calculadora usando os delegates Func<> e Action<>.

note-taking A única diferença entre estes dois delegates é o retorno, enquanto no Func<> você é obrigado a informar o tipo de retorno esperado, o Action não retorna nada, é um void.

Os dois delegates possuem ao todo 16 parâmetros de entrada cada um e o Func<> mais um de retorno.
Eu nunca precisei usar mais que 3, se começar a usar muitos parâmetros, reveja seu código.

Chega de papo e mãos à obra

Abaixo o código completo da calculadora usando estes dois delegates.

using System;

namespace FuncEAction
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            Console.Title = "Calculadora delegate";
            //Criando o ponteiro para o delegate, iniciamos com null, pois não sabemos qual operação iremos realizar.
            Func<double, double, double> handler = null;
            //Ação que será executada
            Action action = null;

            //while(true) ... Yes ...
            while(true)
            {
                string operacao = LerOperacao();

                //de acordo com a operação utilizada, iremos istanciar o nosso delegate e executar o método associado
                switch(operacao)
                {
                    case "+":
                        handler = Somar;
                        break;
                    case "-":
                        handler = Subtrair;
                        break;
                    case "*":
                        handler = Multiplicar;
                        break;
                    case "/":
                        handler = Dividir;
                        break;
                    case "s":
                        action = Exit;
                        break;
                    case "c":
                        action = Clear;
                        break;
                    default:
                        Console.WriteLine("Operação não implementada.");
                        continue;
                }

                if(action != null)
                {
                    //invocar a ação do mal
                    action();
                    action = null;
                    continue;
                }

                double x = LerNumero("primeiro");
                double y = LerNumero("segundo");

                //Executa o método associado
                Console.WriteLine($"\r\nO resultado da operação {x:N2} {operacao} {y:N2} é {handler(x, y):N2}");

                Console.ReadKey();
            }
        }

        #region Métodos de leitura

        private static string LerOperacao()
        {
            string validas = "+-*/cs";
            string result = "";

            do
            {
                DrawMenu();
                result = Console.ReadKey().KeyChar.ToString();

                if(!validas.Contains(result))
                {
                    Console.WriteLine("\r\nInforme uma operação válida.");
                    result = "";
                    Console.ReadKey();
                }
            } while(String.IsNullOrWhiteSpace(result));

            return result;
        }

        private static double LerNumero(string text)
        {
            double result = 0;

            do
            {
                Console.Clear();
                Console.WriteLine($"Informe o {text} número da operação.");
                string numero = Console.ReadLine();

                if(!double.TryParse(numero, out result))
                {
                    Console.WriteLine("\r\nInforme um número válido.");
                    Console.ReadKey();
                }
            } while(result == 0);

            return result;
        }

        #endregion Métodos de leitura

        #region Métodos de operação

        private static double Somar(double x, double y)
        {
            return x + y;
        }

        private static double Dividir(double x, double y)
        {
            return x / y;
        }

        private static double Multiplicar(double x, double y)
        {
            return x * y;
        }

        private static double Subtrair(double x, double y)
        {
            return x - y;
        }

        #endregion Métodos de operação

        #region Métodos de ação

        private static void Exit()
        {
            Environment.Exit(0);
        }

        private static void Clear()
        {
            Console.Clear();
        }

        private static void DrawMenu()
        {
            Console.WriteLine("Informe a operação desejada:");
            Console.WriteLine("(+) para Somar.");
            Console.WriteLine("(-) para Subtrair.");
            Console.WriteLine("(/) para Dividir.");
            Console.WriteLine("(*) para Multiplicar.");
            Console.WriteLine("(c) para Limpar o log.");
            Console.WriteLine("(s) para Sair.");
        }

        #endregion Métodos de ação
    }
}

Predicate

O tipo Predicate é um delegate especial que possui apenas um parâmetro de entrada e retorna um booleano.
Deve ser utilizado quando precisamos informar uma condição para que a ação seja executada. O nosso predicado, pode executar quantas situações forem necessárias para satisfazer a condição.

No código abaixo, iremos ver um exemplo de uso do Predicate e do Action por classes do C#.

Repare que iremos usar a classe “Array

    internal class Program
    {
        private static void Main(string[] args)
        {
            //Array com o nome dos países
            string[] countries ={
                    "Afeganistão","África do Sul","Akrotiri","Albânia","Alemanha","Andorra","Angola","Anguila","Antártida","Antígua e Barbuda","Antilhas","Arábia Saudita","Arctic Ocean","Argélia","Argentina","Arménia","Aruba","Ashmore andCartier Islands","Atlantic Ocean","Austrália","Áustria","Azerbaijão","Baamas","Bangladeche","Barbados","Barém","Bélgica","Belize","Benim","Bermudas","Bielorrússia","Birmânia","Bolívia","Bósnia e Herzegovina","Botsuana","Brasil","Brunei","Bulgária","Burquina Faso","Burúndi","Butão","Cabo Verde","Camarões","Camboja","Canadá","Catar","Cazaquistão","Chade","Chile","China","Chipre","ClippertonIsland","Colômbia","Comores","Congo-Brazzaville","Congo-Kinshasa","Coral Sea Islands","Coreia do Norte","Coreia doSul","Costa do Marfim","Costa Rica","Croácia","Cuba","Dhekelia","Dinamarca","Domínica","Egipto","EmiratosÁrabes Unidos","Equador","Eritreia","Eslováquia","Eslovénia","Espanha","Estados Unidos","Estónia","Etiópia","Faroé","Fiji","Filipinas","Finlândia","França","Gabão","Gâmbia","Gana","GazaStrip","Geórgia","Geórgia do Sul e Sandwich do Sul","Gibraltar","Granada","Grécia","Gronelândia","Guame","Guatemala","Guernsey","Guiana","Guiné","Guiné Equatorial","Guiné-Bissau","Haiti","Honduras","Hong Kong","Hungria","Iêmen","Ilha Bouvet","Ilha do Natal","Ilha Norfolk","Ilhas Caimão","Ilhas Cook","Ilhas dos Cocos","Ilhas Falkland","Ilhas Heard e McDonald","Ilhas Marshall","Ilhas Salomão","Ilhas Turcas e Caicos","Ilhas Virgens Americanas","Ilhas VirgensBritânicas","Índia","Indian Ocean","Indonésia","Irão","Iraque","Irlanda","Islândia","Israel","Itália","Jamaica","Jan Mayen","Japão","Jersey","Jibuti","Jordânia","Kuwait","Laos","Lesoto","Letónia","Líbano","Libéria","Líbia","Listenstaine","Lituânia","Luxemburgo","Macau","Macedónia","Madagáscar","Malásia","Malávi","Maldivas","Mali","Malta","Man, Isle of","Marianas do Norte","Marrocos","Maurícia","Mauritânia","Mayotte","México","Micronésia","Moçambique","Moldávia","Mónaco","Mongólia","Monserrate","Montenegro","Mundo","Namíbia","Nauru","Navassa Island","Nepal","Nicarágua","Níger","Nigéria","Niue","Noruega","Nova Caledónia","Nova Zelândia","Omã","Pacific Ocean","Países Baixos","Palau","Panamá","Papua-Nova Guiné","Paquistão","Paracel Islands","Paraguai","Peru","Pitcairn","Polinésia Francesa","Polónia","Porto Rico","Portugal","Quénia","Quirguizistão","Quiribáti","Reino Unido","República Centro-Africana","República Checa","República Dominicana","Roménia","Ruanda","Rússia","Salvador","Samoa","Samoa Americana","Santa Helena","Santa Lúcia","São Cristóvão e Neves","São Marinho","São Pedro e Miquelon","São Tomé e Príncipe","São Vicente e Granadinas","Sara Ocidental","Seicheles","Senegal","Serra Leoa","Sérvia","Singapura","Síria","Somália","Southern Ocean","Spratly Islands","Sri Lanca","Suazilândia","Sudão","Suécia","Suíça","Suriname","Svalbard e Jan Mayen","Tailândia","Taiwan","Tajiquistão","Tanzânia","Território Britânico do Oceano Índico","Territórios Austrais Franceses","Timor Leste","Togo","Tokelau","Tonga","Trindade e Tobago","Tunísia","Turquemenistão","Turquia","Tuvalu","Ucrânia","Uganda","União Europeia","Uruguai","Usbequistão","Vanuatu","Vaticano","Venezuela","Vietname","Wake Island","Wallis e Futuna","West Bank","Zâmbia","Zimbabué"
                };

            //imprimir array
            Console.WriteLine("\r\nImprimindo países\r\n");
            Action<string> printArray = (item) => { Console.WriteLine(item); };
            Array.ForEach(countries, printArray);

            //encontrar um último da letra M
            Console.WriteLine("\r\nÚltimo com a letra M\r\n");
            Predicate<string> findLastM = (item) =>
            {
                return item.StartsWith("M", StringComparison.InvariantCultureIgnoreCase);
            };
            Console.WriteLine($"O último com a letra M é {Array.FindLast(countries, findLastM)}");

            //imprimir todos que tem a letra k
            Console.WriteLine("\r\nTodos que tem a letra K\r\n");
            Predicate<string> hasK = (item) =>
            {
                return item.Contains("k");
            };

            string[] ks = Array.FindAll(countries, hasK);
            Array.ForEach(ks, printArray);

            Console.ReadKey();
        }
    }
question Lindo isso, mas eu realmente vou usar?

Sim, vai. Aguarde novidades nos próximos capítulos, quando entrarmos no assunto expressões lambda.

 


É isso ai pessoal 🙂
Até o próximo
♦ Marcelo

About Marcelo

Nascido em Juruaia/MG em uma fazenda de criação de búfalos, e residindo na região Sul do Brasil. Trabalha com desenvolvimento de aplicações desde os 17 anos. Atualmente é Arquiteto Organizacional na Unimake Software. Para saber mais ... http://desenvolvedores.net/marcelo []'s

Delegates e Eventos

0
1 Estrela2 Estrelas3 Estrelas4 Estrelas5 Estrelas (1 votos, média: 5,00 de 5)
Loading...
28 de junho de 2016

delegateAndEvents

Olá galera. 🙂

Veja o índice completo do tópico “Delegate”

Continuando nossa série sobre delegates, desta vez iremos falar sobre eventos, e quase impossível não entrar no assunto quando estamos falando de delegates.
Neste artigo iremos aprender a escrever os eventos de forma complexa e simples, com o uso da palavra reservada “event“.

note-taking Eventos, de forma geral, é um meio da classe, ou componente enviar mensagens aos seus ouvintes quando algo interessante acontece. Os eventos em C#, utilizam-se de delegates para a troca de mensagens.
Como por exemplo:

  • O Clique do botão;
  • O movimento do cursor;
  • O pressionamento de uma tecla;
  • Uma ação ocorrida;

Entenda eventos como mudanças ou ações que precisam ser notificadas para seus ouvintes, listenners, através de seus objetos, senders.

Além do uso tradicional, iremos ver neste artigo:

  • Eventos em herança;
  • Eventos em interfaces;

Criando eventos

Os eventos em C# são definidos em três passos:

  • Primeiro, criamos o delegate que irá tratar os métodos que serão chamados pelo evento. Opcionalmente podemos usar o delegate “System.EventHandler
  • Segundo, criamos a classe de argumentos para o evento;
  • Terceiro, criamos o evento com o uso da palavra reservada “event“.

Esta declaração segue o padrão de criação de eventos em C#.

Abaixo, iremos ver um exemplo simples da criação de eventos, completo e com o delegate genérico “System.EventHandler“. Preste atenção aos comentários no código.

using System;

//Criando eventos seguindo o padrão de criação definido pelo C#
namespace DelegatesEEventos
{
    //declara o tratador do evento com dois parâmetros
    //Esta declaração pode ser ocultada com o uso do delegate genérico System.EventHandler&amp;lt;&amp;gt;
    //como iremos ver mais abaixo
    public delegate void CustomHandler(object sender, CustomEventArgs args);
    
    //Cria a classe de argumentos do evento
    public class CustomEventArgs: EventArgs
    {
        public string Mensagem { get; private set; }

        public CustomEventArgs(string mensagem)
        {
            Mensagem = mensagem;
        }
    }

    //define uma classe com eventos
    public class ClasseQualquerComEvento
    {
        //Define um evento na classe, usando o nosso delegate
        public event CustomHandler OnFazendoAlgo;

        //Podemos declarar um evento genérico usando o System.EventHandler, 
        //neste caso não é necessário criar o nosso delegate
        public event EventHandler&amp;lt;CustomEventArgs&amp;gt; OnFazendoEventoGenerico;
    }
}

Invocando um evento

Para chamar um evento em C#, basta chamar o mesmo como se fosse um método, passando os parâmetros pedido pelo delegate, seja ele genérico ou não.

   public void FacaAlgo()
        {
            //Diferente do VB, em C# temos que verificar se o evento não é nulo antes de chamar.
            if(OnFazendoAlgo != null)
                //Basta chamar o evento como se fosse um método da classe, passando os parâmetros definidos no delegate
                OnFazendoAlgo(this, new CustomEventArgs("Olá, estou fazendo algo pelo handler customizado"));

            if(OnFazendoEventoGenerico != null)
                //O mesmo acontece com o delegate genérico
                OnFazendoEventoGenerico(this, new CustomEventArgs("Estou fazendo algo pelo delegate genérico"));
        }

O que acontece por baixo?

UnderTheHood O que acontece por baixo?

Aprendemos a criar o evento usando a palavra reservada “event” para diminuir o tamanho de código digitado. Mas, o que realmente acontece por baixo dos panos?

Quando o compilador encontra a palavra reservada “event“, é criado dois novos métodos pelo C# um “add” e um “remove” seguidos de “_NomeDoEvento“, mas nós também podemos usar esta técnica com as palavras reservadas “add” e “remove“. Veja como ficaria a nossa declaração de eventos no exemplo abaixo:

using System;

//Criando eventos seguindo o padrão de criação definido pelo C#
namespace DelegatesEEventos.AddRemove
{
    //declara o tratador do evento com dois parâmetros
    //Esta declaração pode ser ocultada com o uso do delegate genérico System.EventHandler&lt;&gt;
    //como iremos ver mais abaixo
    public delegate void CustomHandler(object sender, CustomEventArgs args);

    //Cria a classe de argumentos do evento
    public class CustomEventArgs: EventArgs
    {
        public string Mensagem { get; private set; }

        public CustomEventArgs(string mensagem)
        {
            Mensagem = mensagem;
        }
    }

    //define uma classe com eventos
    public class ClasseQualquerComEvento
    {
        //Definimos a instancia do nosso delegate para tratar os eventos
        private CustomHandler customHandler;

        //Define um evento na classe, usando o nosso delegate
        public event CustomHandler OnFazendoAlgo
        {
            add
            {
                lock (customHandler)
                {
                    //adiciona um evento à lista de chamadas
                    customHandler += value;
                }
            }
            remove
            {
                lock (customHandler)
                {
                    //remove um evento da lista de chamadas
                    customHandler -= value;
                }
            }
        }

        public void FacaAlgo()
        {
            //Diferente do VB, em C# temos que verificar se o evento não é nulo antes de chamar.
            if(customHandler != null)
                //Basta chamar o evento como se fosse um método da classe, passando os parâmetros definidos no delegate
                customHandler.Invoke(this, new CustomEventArgs("Olá, estou fazendo algo pelo add/remove"));
        }
    }
}
note-taking Esta abordagem foi apenas para mostrar que podemos criar os métodos de acesso ao evento. Na maioria dos casos a forma tradicional atende.

Eventos em Herança

Vou mostrar duas formas de realizar a chamada de eventos por herança em C#, depois iremos comentar as duas formas e mostrar qual a melhor.

Quando criamos classes, temos que ter em mente que os eventos são um tipo especial de delegates que só podem ser invocados dentro da própria classe, as classes filhas não podem executar estes eventos diretamente.

question Mas como posso fazer com que as minhas classes filhas utilizem os eventos da classe pai?

Para ilustrar a chamada, vamos analisar o código abaixo.

using System;

//Criando eventos seguindo o padrão de criação definido pelo C#
namespace DelegatesEEventos.Heranca
{
    //declara o tratador do evento com dois parâmetros
    //Esta declaração pode ser ocultada com o uso do delegate genérico System.EventHandler<>
    //como iremos ver mais abaixo
    public delegate void CustomHandler(object sender, CustomEventArgs args);

    //Cria a classe de argumentos do evento
    public class CustomEventArgs: EventArgs
    {
        public string Mensagem { get; private set; }

        public CustomEventArgs(string mensagem)
        {
            Mensagem = mensagem;
        }
    }

    //define uma classe com eventos
    public class ClasseQualquerComEvento
    {
        //Define um evento na classe, usando o nosso delegate
        //Veja, marcamos como "virtual" o nosso evento
        public virtual event CustomHandler OnFazendoAlgo;

        public void FacaAlgo()
        {
            //Diferente do VB, em C# temos que verificar se o evento não é nulo antes de chamar.
            if(OnFazendoAlgo != null)
                //Basta chamar o evento como se fosse um método da classe, passando os parâmetros definidos no delegate
                OnFazendoAlgo(this, new CustomEventArgs("Olá, estou fazendo algo pelo método FacaAlgo"));
        }
    }

    //classe herdada
    public class ClasseQualquerComEventoHeranca: ClasseQualquerComEvento
    {
        //Sobrescrevemos o evento que queremos chamar, pois como já foi dito, só podemos 
        //chamar eventos dentro da classe
        public override event CustomHandler OnFazendoAlgo;

        //método qualquer que utiliza o evento
        public void FacaAlgoFilho()
        {
            //Como sobrescrevemos o evento, podemos chamá-lo aqui
            if(OnFazendoAlgo != null)
                OnFazendoAlgo(this, new CustomEventArgs("Olá, estou fazendo algo pelo método filho"));
        }
    }
}
errado Isto está errado.
A abordagem acima está errada, pois ao chamar o método “FacaAlgo()” da classe pai o evento não será lançado.

ClasseQualquerComEventoHeranca classeQualquerComEventoHeranca = new ClasseQualquerComEventoHeranca();
            classeQualquerComEventoHeranca.OnFazendoAlgo += (s, a) =>
            {
                Console.WriteLine(a.Mensagem);
            };
            classeQualquerComEventoHeranca.FacaAlgo();
            classeQualquerComEventoHeranca.FacaAlgoFilho();
            /* Output:

            Olá, estou fazendo algo pelo método filho

            */

 

note-taking Não marque seus eventos como virtuais, o compilador do C# não irá definir a chamada corretamente, e ao sobrescrever o evento apenas a classe filha atende ao delegate. A ação definida no evento para o  método pai não será invocada.

O padrão correto

Agora, vamos utilizar o padrão correto para este tipo de caso.

  • Crie seu evento como já foi mostrado;
  • Crie um método protegido e virtual para notificar que o evento deverá ocorrer;
using System;

//Criando eventos seguindo o padrão de criação definido pelo C#
namespace DelegatesEEventos.Heranca
{
    //declara o tratador do evento com dois parâmetros
    //Esta declaração pode ser ocultada com o uso do delegate genérico System.EventHandler<>
    //como iremos ver mais abaixo
    public delegate void CustomHandler(object sender, CustomEventArgs args);

    //Cria a classe de argumentos do evento
    public class CustomEventArgs: EventArgs
    {
        public string Mensagem { get; private set; }

        public CustomEventArgs(string mensagem)
        {
            Mensagem = mensagem;
        }
    }

    //define uma classe com eventos
    public class ClasseQualquerComEvento
    {
        //Define um evento na classe, usando o nosso delegate
        //Veja, marcamos como "virtual" o nosso evento
        public event CustomHandler OnFazendoAlgo;

        public void FacaAlgo()
        {
            FazendoAlgoNotify(new CustomEventArgs("Olá, estou fazendo algo pelo método FacaAlgo"));
        }

        //Define o método de notificãção do evento OnFazendoAlgo
        protected virtual void FazendoAlgoNotify(CustomEventArgs args)
        {
            //Diferente do VB, em C# temos que verificar se o evento não é nulo antes de chamar.
            if(OnFazendoAlgo != null)
                //Basta chamar o evento como se fosse um método da classe, passando os parâmetros definidos no delegate
                OnFazendoAlgo(this, args);
        }
    }

    //classe herdada
    public class ClasseQualquerComEventoHeranca: ClasseQualquerComEvento
    {
        //método qualquer que utiliza o evento
        public void FacaAlgoFilho()
        {
            //Como sobrescrevemos o evento, podemos chamá-lo aqui
            FazendoAlgoNotify(new CustomEventArgs("Olá, estou fazendo algo pelo método filho"));
        }

        //se precisar, pode sobrescrever o método
        protected override void FazendoAlgoNotify(CustomEventArgs args)
        {
            //Se necessário. Faça alguma ação antes aqui

            //depois chama o método de base
            base.FazendoAlgoNotify(args);
        }
    }
}
certo Isto está correto.

Como podem ver, ao chamar os dois métodos, o evento será executado duas vezes, pois cada método quer notificar que fez algo.

ClasseQualquerComEventoHeranca classeQualquerComEventoHeranca = new ClasseQualquerComEventoHeranca();
            classeQualquerComEventoHeranca.OnFazendoAlgo += (s, a) =>
            {
                Console.WriteLine(a.Mensagem);
            };
            classeQualquerComEventoHeranca.FacaAlgo();
            classeQualquerComEventoHeranca.FacaAlgoFilho();
            /* Output:

            Olá, estou fazendo algo pelo método FacaAlgo
            Olá, estou fazendo algo pelo método filho

            */

Eventos em interfaces

Interfaces permitem definir a assinatura de seus eventos. Para isso, declare-os normalmente na interface.

Veja o exemplo:

using System;

namespace DelegatesEEventos.Interfaces
{
    //declara o tratador do evento com dois parâmetros
    public delegate void CustomHandler(object sender, CustomEventArgs args);

    //Cria a classe de argumentos do evento
    public class CustomEventArgs: EventArgs
    {
        public string Mensagem { get; private set; }

        public CustomEventArgs(string mensagem)
        {
            Mensagem = mensagem;
        }
    }

    //Define a interface
    public interface IImplementotoEvento
    {
        //Define o evento na interface
        event CustomHandler OnFazendoAlgo;

        //método faça algo
        void FacaAlgo();
    }

    //define uma classe com eventos, e implementa a interface IImplentoEvento
    public class ClasseQualquerComEventoEInterface: IImplementotoEvento
    {
        //Define o evento implementado pela interface
        public event CustomHandler OnFazendoAlgo;

        //define o método implementado na interface
        public void FacaAlgo()
        {
            //Diferente do VB, em C# temos que verificar se o evento não é nulo antes de chamar.
            if(OnFazendoAlgo != null)
                //Basta chamar o evento como se fosse um método da classe, passando os parâmetros definidos no delegate
                OnFazendoAlgo(this, new CustomEventArgs("Olá, estou fazendo algo."));
        }
    }
}

Para chamar, podemos criar uma instância da interface ou a classe diretamente, mas para o exemplo, iremos criar uma instância do tipo da interface.

IImplementotoEvento implentoEvento = new ClasseQualquerComEventoEInterface();
            implentoEvento.OnFazendoAlgo += (s, a) =>
            {
                Console.WriteLine(a.Mensagem);
            };
            implentoEvento.FacaAlgo();

            /* Output:

            Olá, estou fazendo algo.

            */

 


É isso ai pessoal 🙂
Até o próximo
♦ Marcelo

About Marcelo

Nascido em Juruaia/MG em uma fazenda de criação de búfalos, e residindo na região Sul do Brasil. Trabalha com desenvolvimento de aplicações desde os 17 anos. Atualmente é Arquiteto Organizacional na Unimake Software. Para saber mais ... http://desenvolvedores.net/marcelo []'s

Delegate

0
1 Estrela2 Estrelas3 Estrelas4 Estrelas5 Estrelas (2 votos, média: 5,00 de 5)
Loading...
24 de junho de 2016

delegate

Olá Amigos.

Neste tópico estarei falando sobre delegates, sobre sua utilização em eventos, em funções, em ações, em lambda expressions, sobre multicasting e a construção de expressões lambdas e Linq.

Iremos começar da base, para entendermos o que realmente é um delegate e como ele pode nos ser útil. Iremos começar com exemplo simples e rápidos, ao modo que cada artigo vai sendo escrito, iremos nos aprofundando mais neste tópico, até chegarmos ao ponto de escrevermos nossas próprias expressões lambdas.

Índice

Vou apontar o índice para os demais artigos conforme forem sendo escritos

O que é delegate?

note-taking Podemos dizer que um delegate é uma função de retorno (callback function), um ponteiro para uma função que será chamada em outro momento. Com esta peculiaridade, podemos tratar o clique de um botão, o movimento do mouse, a seleção de um menu. O que chamamos de eventos.
Um delegate suporta chamadas assíncronas, síncronas e multicasting.
Iremos falar disso em outros tópicos

Neste artigo, iremos analisar sobre os diversos aspectos de um delegate, como criá-lo e como instanciá-lo e como utilizar o delegate.

Compreendendo o Delegate

Diferente dos ponteiros programados usando C que são apontamentos direto para um endereço de memória, os delegates em C# são mais seguros e fortemente tipados, mais orientado a objetos, um delegate é um type-safe

note-taking Type-Safe é a capacidade do C# de garantir que um objeto tem suas características preservadas. Está também ligado à forma de acesso em memória deste objeto.

Exemplo:
Em C, podemos declarar algo assim, de forma insegura;
Você declara um inteiro, faz um cast (conversão) para um tipo char e consegue acessar fora dos limites do int.

int i = 10;
char *s = (char*)i;
print(*(s+10));

Em c# isso não é possível, a não ser que você use a palavra chave “unsafe (inseguro)

int i = 10;
char *s >> //isto não será permitido fora de um contexto unsafe (inseguro)

Um delegate mantem 3 informações importantes:

      O endereço do método para o qual deverá ser chamado
      Os parâmetros, se existirem
      O retorno, se existir
note-taking Delegates podem apontar para métodos estáticos ou de instância.

Quando um objeto do tipo delegate é criado ele já vem adaptado para ser chamado de forma síncrona ou assíncrona, isto é muito útil quando iremos programar pensando em multi-tarefas, sem ter que criar um objeto de tarefas (Thread).

Iremos falar sobre este assunto,Multi-Tasking, em outro momento.

Definindo um delegate

A definição do delegate em C# segue esta regra:
modificador de acesso delegate retorno (ou void)nome (parâmetros (opcionais))

public delegate int FacaAlgoERetorne(int x, int y);
public delegate void SoFacaAlgo(int x, int y);
public delegate void FacaAlgo();

Quando o compilador C# interpretar o nosso objeto delegate, ele automaticamente irá criar uma classe que herda de “System.MulticastDelegate” esta classe terá os métodos que iremos utilizar para executar nosso delegate quando necessário. Iremos nos ater apenas ao método “Invoke()”

MaosAObra Chega de papo e mãos à obra

Mãos à obra

Para reforçar o entendimento, iremos escrever uma calculadora simples, que fará as quatro operações básicas. Abaixo o código completo em C#

using System;

namespace CalculadoraDelegate
{
    internal class Program
    {
        //========================================================
        //Criação do nosso delegate
        private delegate double Operacao(double x, double y);
        //========================================================

        /* veja que nosso delegate tem:
         * Modificador de acesso (private)
         * palavra reservada (delegate)
         * Tipo de retorno (double)
         * Parâmetros (x e y)
         * Isso determina como deverá ser a assinatura do método que será utilizado para o nosso delegate.
         * Caso o método não tenha esta assinatura o erro "Error CS0123	No overload for 'Nome do Método' matches delegate 'namespace.NomeDoDelegate'" será lançado
         */

        private static void Main(string[] args)
        {
            Console.Title = "Calculador delegate (CTRL + C para sair)";
            double x = LerNumero();
            string operacao = LerOperacao();
            double y = LerNumero();

            //========================================================
            //Criando o ponteiro para o delegate, iniciamos com null, pois não sabemos qual operação iremos realizar.
            Operacao handler = null;
            //========================================================

            //de acordo com a operação utilizada, iremos istanciar o nosso delegate e executar o método associado
            switch(operacao)
            {
                case "+":
                    handler = new Operacao(Somar);
                    break;
                case "-":
                    handler = new Operacao(Subtrair);
                    break;
                case "*":
                    handler = new Operacao(Multiplicar);
                    break;
                case "/":
                    handler = new Operacao(Dividir);
                    break;
                default:
                    Console.WriteLine("Operação não implementada.");
                    break;
            }

            //Executa o método associado
            Console.WriteLine($"\r\nO resultado da operação {x:N2}{operacao}{y:N2} é {handler.Invoke(x, y):N2}");

            Console.ReadKey();
        }

        #region Métodos de leitura

        private static string LerOperacao()
        {
            string validas = "+-*/";
            string result = "";

            do
            {
                Console.Clear();
                Console.WriteLine("Informe a operação desejada:");
                Console.WriteLine("(+) para Somar.");
                Console.WriteLine("(-) para Subtrair.");
                Console.WriteLine("(/) para Dividir.");
                Console.WriteLine("(*) para Multiplicar.");
                result = Console.ReadKey().KeyChar.ToString();

                if(!validas.Contains(result))
                {
                    Console.WriteLine("\r\nInforme uma operação válida.");
                    result = "";
                    Console.ReadKey();
                }
            } while(String.IsNullOrWhiteSpace(result));

            return result;
        }

        private static double LerNumero()
        {
            double result = 0;

            do
            {
                Console.Clear();
                Console.WriteLine("Informe um número.");
                string numero = Console.ReadLine();

                if(!double.TryParse(numero, out result))
                {
                    Console.WriteLine("\r\nInforme um número válido.");
                    Console.ReadKey();
                }
            } while(result == 0);

            return result;
        }

        #endregion Métodos de leitura

        #region Métodos de operação

        private static double Somar(double x, double y)
        {
            return x + y;
        }

        private static double Dividir(double x, double y)
        {
            return x / y;
        }

        private static double Multiplicar(double x, double y)
        {
            return x * y;
        }

        private static double Subtrair(double x, double y)
        {
            return x - y;
        }

        #endregion Métodos de operação
    }
}

Entendendo o código

Como podemos ver, a nossa calculadora utiliza o mesmo delegate nas quatro operações.

private delegate double Operacao(double x, double y);

Conforma a operação é definida o delegate é instanciado passando os parâmetros para o mesmo:

//de acordo com a operação utilizada, iremos istanciar o nosso delegate e executar o método associado
            switch(operacao)
            {
                case "+":
                    handler = new Operacao(Somar);
                    break;
                case "-":
                    handler = new Operacao(Subtrair);
                    break;
                case "*":
                    handler = new Operacao(Multiplicar);
                    break;
                case "/":
                    handler = new Operacao(Dividir);
                    break;
                default:
                    Console.WriteLine("Operação não implementada.");
                    break;
            }

E ao final, é invocado e recuperado o resultado da operação.

//Executa o método associado
            Console.WriteLine($"\r\nO resultado da operação {x:N2}{operacao}{y:N2} é {handler.Invoke(x, y):N2}");

 


É isso ai pessoal 🙂
Até o próximo
♦ Marcelo

About Marcelo

Nascido em Juruaia/MG em uma fazenda de criação de búfalos, e residindo na região Sul do Brasil. Trabalha com desenvolvimento de aplicações desde os 17 anos. Atualmente é Arquiteto Organizacional na Unimake Software. Para saber mais ... http://desenvolvedores.net/marcelo []'s

Criando componentes no lado Servidor

1
1 Estrela2 Estrelas3 Estrelas4 Estrelas5 Estrelas (Sem votação.)
Loading...
20 de junho de 2014

CriandoComponentes

Para um melhor entendimento deste Artigo veja o Índice (Rich Internet Applications com Ext.Net)

Criando componentes no lado Servidor

Ao desenvolvermos um componente utilizando como base um componente já criado no Ext.NET, ganhamos de brinde toda a reutilização deste, por exemplo: – Nosso componente já pode ser utilizado em WebForm,  em ASP MVC, com a engine Razor, no código markup, etc.

Outra vantagem desta abordagem é que quando estudamos o código do Ext.NET, você poderá entender como eles criam seus componentes com base nos componentes Ext JS,e não necessariamente você precisa ser um conhecedor profundo do Ext JS.

Como vantagem também, podemos citar que você pode desenvolver seu componente em qualquer linguagem .NET que você tenha familiaridade e não precisa aprender a ser um ninja em javascript, pois todo o javascript será gerado para o lado cliente.

Neste tópico, iremos focar na criação do componente do lado servidor, na utilização do lado cliente, e como criar seus próprios métodos que podem ser acessados do lado cliente e lado servidor.

Antes de começarmos…

Vamos definir nossas abstrações e nossos objetos de validação do componente. Como neste artigo eu irei criar dois componentes para inserção de texto. Iremos criar uma abstração para tipos texto e uma classe de resultado da validação.

Classe ValidateResult

/// <summary>
/// Classe que determina o resultado da validação feita e retorna.
/// </summary>
public sealed class ValidateResult
{
	#region locais
	bool valid = true;
	string invalidMessage = "";
	#endregion

	#region propriedades
	/// <summary>
	/// Se true, o valor inserido no componente é valido.
	/// </summary>
	public bool Valid
	{
		get { return valid; }
		private set { valid = value; }
	}

	/// <summary>
	/// Se não for válido é necessário informar uma mensagem de erro
	/// </summary>
	public string InvalidMessage
	{
		get { return invalidMessage; }
		private set { invalidMessage = value; }
	}
	#endregion

	#region Construtores
	/// <summary>
	/// Inicia o resultado da validação
	/// </summary>
	/// <param name="valid">true/false para válido ou não.</param>
	/// <param name="invalidMessage">Se "valid" for verdadeiro, é obrigatório informar a mensagem.</param>
	/// <exception cref="ArgumentException">Se "valid" for verdadeiro, e o parâmetro "invalidMessage"
	/// não for informado. Esta exceção será lançada.</exception>
	public ValidateResult(bool valid, string invalidMessage = "")
	{
		this.valid = valid;

		if(!valid && String.IsNullOrEmpty(invalidMessage))
			throw new ArgumentException("Se \"valid\" for definido como false. É obrigatório informar uma mensagem de erro em \"invalidMessage\".", "invalidMessage");

		this.invalidMessage = invalidMessage;
	}
	#endregion
}

Abstração para campos do tipo texto (TextFieldBase)

public abstract class TextFieldBase: Ext.Net.TextField, IValidator
{
	public TextFieldBase()
		: base()
	{

	}

	#region IValidator Members
	public string ErrorMessage { get; set; }
	public bool IsValid { get; set; }

	public override void Validate()
	{
		ValidateResult result = Validate(Text);
		IsValid = result.Valid;
		ErrorMessage = result.InvalidMessage;

		if(IsValid)
			MarkAsValid();
		else
			MarkInvalid(ErrorMessage);
	}

	/// <summary>
	/// Faz a validação do componente e retorna true se o texto do componente for válido, caso contrário false
	/// </summary>
	/// <param name="value">valor que deverá ser validado</param>
	/// <returns></returns>
	protected abstract ValidateResult Validate(string value);
	#endregion
}
note-taking No código em destaque, vejam que eu fiz uma herança da classe “Ext.Net.TextField” e da interface “IValidator”.
Desta forma eu já herdo todas as funcionalidades de um componente do tipo “TextField” e apenas ajusto para a minha necessidade. a herança da interface “IValidator” é apenas para a implementação dos métodos de validação.

Ok! Definimos a nossa abstração. Vamos agora criar o nosso componente.

Meu primeiro componente

Vamos criar um componente bem simples. Um campo de CEP, que irá fazer uma validação e será utilizado em uma página.

CEPField

public class CEPField: TextFieldBase
{
	#region Contrutores
	public CEPField()
		: base()
	{
		DirectEvents.Blur.Event += new ComponentDirectEvent.DirectEventHandler(Blur_Event);
		AllowBlank = false;
		this.IsRemoteValidation = true;
		this.RemoteValidation.Validation += new RemoteValidationDirectEvent.RemoteValidationEventHandler(RemoteValidation_Validation);
		FieldLabel = "CEP";
	}
	#endregion

	#region Tratadores de eventos
	void RemoteValidation_Validation(object sender, RemoteValidationEventArgs e)
	{
		Validate();
		e.Success = IsValid;
		e.ErrorMessage = ErrorMessage;
	}

	void Blur_Event(object sender, DirectEventArgs e)
	{
		this.SetValue(Format.CEP(Text));
	}
	#endregion

	#region overrides

	public override void SetValue(object value)
	{
		Validate();

		base.SetValue(value);
		if(!string.IsNullOrEmpty(ClientID))
			Ext.Net.X.AddScript(ClientID +
				".isValid = function(){return " + IsValid.ToString().ToLower() + ";};");
	}

	public override int MaxLength
	{
		get { return 9; }
		set { base.MaxLength = 9; }
	}
	#endregion

	#region IValidator Members
	protected override ValidateResult Validate(string value)
	{
		if(AllowBlank && String.IsNullOrEmpty(value))
			return new ValidateResult(true);

		value = Utilities.OnlyNumbers(value);

		if(String.IsNullOrEmpty(value))
			return new ValidateResult(false, "O CEP não pode ser vazio ou não foi digitado corretamente.");

		//permite somente números
		if(value.Count(w => !Char.IsNumber(w)) > 0)
			return new ValidateResult(false, "O CEP não é válido.");

		//para ser válido tem que ter 8 caracteres
		if(value.Length != 8)
			return new ValidateResult(false, "O CEP não é válido.");

		return new ValidateResult(true);
	}
	#endregion
}
question Hm! OK! Eu criei meu componente. Mas como eu faço para chamá-lo em uma página asp? É possível utilizar a mesma marcação que temos para o Ext.NET, utilizando a tag “<ext>“?

R: Sim, é possível, com um pequeno ajuste no nosso arquivo “web.config”.
Dentro da tag “” devemos inserir a linha em destaque, logo abaixo

<pages>
  <controls>
	<!--Indica que toda página criada irá ter a diretiva "ext" como padrão para ser usada como marcação na criação.-->
	<add assembly="Ext.Net" namespace="Ext.Net" tagPrefix="ext"/>
	<add assembly="Ext.Net.Tutorial" namespace="Ext.Net.Tutorial.ComponentModel" tagPrefix="tut"/>
  </controls>
</pages>
  • Esta linha indica que um assembly deverá ser adicionado; “add assembly“; para todas as páginas criadas;
  • Os controles que estão no “namespace” …
  • Deverão ser associados com o prefixo;”tagPrefix“; “tut“;

Pronto. Agora podemos utilizar o nosso componente apenas escrevendo a tag

<tut:CEPField ID="CEPField1" runat="server" FieldLabel="Informe seu CEP">
 </tut:CEPField>

O próximo componente que será criado é um “CurrencyField“, um campo para inserir valores monetários. Vamos ver o código do mesmo, e explicar logo em seguida.

public class CurrencyField: TextFieldBase
{
	#region Construtores
	public CurrencyField()
		: base()
	{
		//-------------------------------------------------------------------------
		// Define que só podemos digitar números de 0-9 e a vírgula.
		//-------------------------------------------------------------------------
		MaskRe = "/[0-9\\,]/";

		//-------------------------------------------------------------------------
		// Define o método javascript que irá ser chamado ao receber o foco
		//-------------------------------------------------------------------------
		Listeners.Focus.Handler = "this.setRawValue(this.getNumber());this.selectText();";

		//-------------------------------------------------------------------------
		// Define o método que será chamado ao perder o foco
		//-------------------------------------------------------------------------
		Listeners.Blur.Handler = "this.setValue(this._getValue(this.getValue()));";

		//-------------------------------------------------------------------------
		// Define que o valor deverá ser alinhado a direta do componente
		//-------------------------------------------------------------------------
		FieldStyle = "text-align:right;";
	}

	#endregion

	#region Overrides
	public override ConfigItemCollection CustomConfig
	{
		get
		{
			ConfigItemCollection result = base.CustomConfig;
			string curSbl = "R$";

			#region _getValue
			//-------------------------------------------------------------------------
			// Criando o método _getValue para o lado cliente.
			//-------------------------------------------------------------------------
			ConfigItem it = new ConfigItem("_getValue", @"function(v){
				return MyApp.Util.Format.currency(this.cleanValue(v), 2,'" + curSbl + @"');
				}
			");

			result.Add(it);
			#endregion

			#region getNumber
			//-------------------------------------------------------------------------
			// Criando o método getNumber para o lado cliente.
			//-------------------------------------------------------------------------
			it = new ConfigItem("getNumber", @"function(){
				if(this.number === undefined || this.number == 0)
					this.number = MyApp.Convert.toNumber(this.getValue());

				return this.number;
				}
			");

			result.Add(it);
			#endregion

			#region setValue
			//-------------------------------------------------------------------------
			// Substituindo o método setValue, com a nossa definição
			//-------------------------------------------------------------------------
			it = new ConfigItem("setValue", @"function(v){
				v = this._getValue(v);
				this.setRawValue(v);
				this.number = MyApp.Convert.toNumber(this.getValue());
				this.text = v;
				}
			");

			result.Add(it);
			#endregion

			#region cleanValue
			//-------------------------------------------------------------------------
			// Criando o método cleanValue para o lado cliente.
			//-------------------------------------------------------------------------
			it = new ConfigItem("cleanValue", @"function(v){
					if(v){
						return MyApp.Util.Format.number(v, 2);
					}else
						return 0;
				}
			");

			result.Add(it);
			#endregion

			return result;
		}
	}

	public override void SetValue(object value)
	{
		this.SetValue(value.ToString());
		base.SetValue(Number);
	}
	protected void SetValue(string text)
	{
		Text = text;
		Number = Convert.ToDouble(text);
	}
	#endregion

	#region Propriedades
	public virtual double Number
	{
		get { return Utilities.Convert.ToDouble(base.Text); }
		set { base.Text = value.ToString(); }
	}
	#endregion

	#region IValidator members
	protected override ValidateResult Validate(string value)
	{
		double d = Utilities.Convert.ToDouble(value);

		if(!AllowBlank && d == 0)
			return new ValidateResult(false, "É obrigatório informar o valor monetário.");

		return new ValidateResult(true);
	}
	#endregion
}

Neste código foi apresentado um meio de você criar seus próprios métodos javascript e associá-los ao seus componentes.

Para isto, utilizamos o que chamamos de “CustomConfig“, veja nas linhas em destaque.

note-taking O objeto “Ext.Net.ConfigItem“, permite que você defina métodos e propriedades para seus controles. Feito isso, você pode acessar em um código javascript.

var v = App.MyComponent._getValue();

Entendendo o objeto Ext.Net.ConfigItem

Este objeto tem a finalidade de vincular seus métodos javascript aos componentes Ext.NET, isso se torna útil quando estamos criando nossos próprios componentes.

Possui 3 propriedades importantes:

  • Name: Define o nome do método ou propriedade que será exposto;
  • Value:  Define o valor que será representado pelo sue método ou propriedade;
  • Mode: O mode pode ser:
    • Auto: O Ext.NET tentará detectar o tipo e se não for capaz, A propriedade “Value” será encapsulada como uma string, ou como um número, de acordo com seu tipo;
    • Raw: A propriedade “Value” será exposta como está;
    • Value: Valor da propriedade ou código do método;
note-taking Por convenção, nomeie seus métodos utilizando “camelCase“. Se você escrever o nome do método em “UpperCase” o Ext.NET irá converter para “camelCase“, mantendo a convenção de nomes definida pelo javascript.

 


É isso ai pessoal 🙂
Até o próximo
♦ Marcelo

About Marcelo

Nascido em Juruaia/MG em uma fazenda de criação de búfalos, e residindo na região Sul do Brasil. Trabalha com desenvolvimento de aplicações desde os 17 anos. Atualmente é Arquiteto Organizacional na Unimake Software. Para saber mais ... http://desenvolvedores.net/marcelo []'s

Trabalhando com dados, Combobox e GridPanel

0
1 Estrela2 Estrelas3 Estrelas4 Estrelas5 Estrelas (1 votos, média: 4,00 de 5)
Loading...
19 de junho de 2014

Dados_Combobox_GridPanel

Para um melhor entendimento deste Artigo veja o Índice (Rich Internet Applications com Ext.Net)

Trabalhando com dados, Combobox e GridPanel

À essa altura do tópico, já temos conhecimento sobre os componentes, “DirectMethod“, “DirectEvents“, “Listeners“, entre outras coisas. Então é hora de entendermos como podemos exibir os dados aos nossos usuários. Neste tópico eu vou apresentar à vocês a classe “Store“. Então, vamos lá.

Store

O “Store” é um componente que trata os dados entre o cliente e o servidor, normalmente utilizado com componentes do tipo “Grids” e “Comboboxes“. Um “Store” tem a capacidade de filtrar e ordenar os dados, seja local ou remotamente.

Este por sua vez, tem dois pontos importantes:

  • Um “Model“, onde definimos os tipos de dados, nomes de campos, validações, entre outros;
  • Um “Proxy“, responsável pelas requisições entre cliente e servidor, e manter os dados atualizados no lado cliente;
note-taking O componente “Store” tem vários tipos de subclasses, como “JsonStore“, “ArrayStore“, “XMLStore” entre outros. Neste tópico iremos ver os dois mais utilizados “JsonStore” e “ArrayStore“.Mas não se preocupe, quando Ext.NET gera as classes proxyes, automaticamente já é gerado todo o código Ext Js que irá tratar cada tipo de “Store“.

Models

Um “Model” define os dados que o “Store” irá tratar. Em versões anteriores do Ext.NET, este era conhecido como “Record“. No “Model” podemos definir os dados, as validações, nomes para cada campo e formato.

Proxy

Um “Proxy” é utilizado para manter os dados, é uma abstração para o mecanismo de tratamento dos dados e se divide em dois tipos:

  • Client-Side Proxies;
    • MemoryProxy: É um simples “proxy” que mantem os dados na memória do navegador;
    • LocalStorageProxySessionStorageProxy: Usam a nova definição de armazenamento do HTML5. Não funcionam em navegadores mais antigos, pois estes não aceitam HTML5
    • (Eu ainda não estou muito familiarizado com o HTML5 e as novas definições de proxy para o mesmo. Para evitar informação errada, vou parar por aqui 🙂 )

  • Server-Side Proxies;
    • AjaxProxy: Este é um “proxy” de propósito geral, que aceita objetos do tipo array, cada índice é um nome de coluna definido no “model“;
    • JsonPProxy: Permite requisições AJAX “cross-domain“;
    • PageProxy: É um “proxy” que trabalha na página e permite a ligação com eventos no lado servidor;

Entendido o processo de acesso utilizando “Stores”, vamos botar em prática o que aprendemos.

Não se preocupe com os detalhes do código, vamos nos ater apenas ao uso do “Store” e suas peculiaridades.

Antes de começar

Para os dois exemplos, temos que preparar o nosso ambiente antes de começar, para isso, crie dois arquivos de estilo.

combobox.css

.list-item
{
    font: normal 11px tahoma, arial, helvetica, sans-serif;
    padding: 3px 10px 3px 10px;
    border: 1px solid #fff;
    border-bottom: 1px solid #eeeeee;
    white-space: normal;
    color: #555;
}

.list-item h3
{
    display: block;
    font: inherit;
    font-weight: bold;
    margin: 0px;
    color: #222;
}

gridpanel.css

.x-grid-cell-fullName .x-grid-cell-inner
{
    font-family: tahoma, verdana;
    display: block;
    font-weight: normal;
    font-style: normal;
    color: #385F95;
    white-space: normal;
}

.x-grid-rowbody div
{
    margin: 2px 5px 20px 5px !important;
    width: 99%;
    color: Blue;
}

.x-grid-row-expanded td.x-grid-cell
{
    border-bottom-width: 0px;
}

Estes dois arquivos serão responsáveis pela apresentação dos dados no lado cliente.

Continuando …

Exibindo os dados…

Vamos utilizar uma lista de objetos para exibir e tratar os dados do lado cliente. Nos dois exemplos iremos configurar um “Store” para uma “Combbox” e para  uma “GridPanel“.

Veja os exemplos:

Atente apenas ao uso do “Store”. O Resto do código será explicado posteriormente.

GridPanel

ASPX Code

<head id="Head1" runat="server">
    <title>Tutorial Ext.NET</title>
    <link href="../css/gridpanel.css" rel="stylesheet" type="text/css" />
    <script type="text/javascript" src="../Scripts/GridPanel.js"></script>
</head>
<body>
    <form id="Form1" runat="server">
    <ext:ResourceManager ID="ResourceManager1" runat="server" />
    <ext:Window ID="Window1" runat="server" Collapsible="true" Maximizable="true" Icon="Money"
        Title="Orders List" Width="1300" Height="600" X="50" Y="50" Layout="Fit" Closable="false">
        <Items>
            <ext:GridPanel ID="GridPanel1" runat="server" Title="Employees" Frame="true" Header="false"
                Border="false">
                <Store>
                    <ext:Store ID="Store1" runat="server" OnReadData="Store1_ReadData">
                        <Model>
                            <ext:Model ID="Model1" runat="server">
                                <Fields>
                                    <ext:ModelField Name="CustomerID" />
                                    <ext:ModelField Name="EmployeeID" />
                                    <ext:ModelField Name="OrderDate" />
                                    <ext:ModelField Name="RequiredDate" />
                                    <ext:ModelField Name="ShippedDate" />
                                    <ext:ModelField Name="ShipVia" />
                                    <ext:ModelField Name="Freight" />
                                    <ext:ModelField Name="ShipName" />
                                    <ext:ModelField Name="ShipAddress" />
                                    <ext:ModelField Name="ShipCity" />
                                    <ext:ModelField Name="ShipRegion" />
                                    <ext:ModelField Name="ShipPostalCode" />
                                    <ext:ModelField Name="ShipCountry" />
                                </Fields>
                            </ext:Model>
                        </Model>
                    </ext:Store>
                </Store>
                <ColumnModel ID="ColumnModel1" runat="server">
                    <Columns>
                        <ext:Column ID="Column0" runat="server" Text="CustomerID" Width="150" DataIndex="CustomerID">
                        </ext:Column>
                        <ext:Column ID="Column1" runat="server" DataIndex="EmployeeID" Text="EmployeeID"
                            Width="150" />
                        <ext:DateColumn ID="Column2" runat="server" DataIndex="OrderDate" Text="Order Date"
                            Width="150" Format="dd/MM/yyyy" />
                        <ext:DateColumn ID="DateColumn1" runat="server" DataIndex="RequiredDate" Text="Required Date"
                            Width="110" Format="dd/MM/yyyy" />
                        <ext:DateColumn ID="DateColumn2" runat="server" DataIndex="ShippedDate" Text="Shipped Date"
                            Width="110" Format="dd/MM/yyyy" />
                        <ext:Column ID="Column3" runat="server" DataIndex="ShipVia" Text="Ship Via" Width="150" />
                        <ext:Column ID="Column4" runat="server" DataIndex="Freight" Text="Freight" Width="100" />
                    </Columns>
                </ColumnModel>
                <View>
                    <ext:GridView ID="GridView1" runat="server">
                        <GetRowClass Handler="return 'x-grid-row-expanded';" />
                    </ext:GridView>
                </View>
                <SelectionModel>
                    <ext:RowSelectionModel ID="RowSelectionModel1" runat="server" Mode="Single" />
                </SelectionModel>
                <Features>
                    <ext:RowBody ID="RowBody1" runat="server">
                        <GetAdditionalData Handler="orig.rowBody = '<div><span style=\'color: red\'>Ship Information</span>' + '</br/>' + data.ShipName + '</br/>' + data.ShipAddress + '</br/>' + data.ShipCity + (data.ShipRegion == null ? '' : ' - ' + data.ShipRegion) + '</br/>' + (data.ShipPostalCode == null ? '' : data.ShipPostalCode + '</br/>') + data.ShipCountry + '</div>'; orig.rowBodyColspan = record.fields.getCount();" />
                    </ext:RowBody>
                </Features>
                <BottomBar>
                    <ext:PagingToolbar ID="PagingToolbar1" runat="server">
                        <Items>
                            <ext:Label ID="Label1" runat="server" Text="Page size:" />
                            <ext:ToolbarSpacer ID="ToolbarSpacer1" runat="server" Width="10" />
                            <ext:ComboBox ID="ComboBox1" runat="server" Width="80">
                                <Items>
                                    <ext:ListItem Text="1" />
                                    <ext:ListItem Text="2" />
                                    <ext:ListItem Text="10" />
                                    <ext:ListItem Text="20" />
                                </Items>
                                <SelectedItems>
                                    <ext:ListItem Value="10" />
                                </SelectedItems>
                                <Listeners>
                                    <Select Handler="#{GridPanel1}.store.pageSize = parseInt(this.getValue(), 10); #{GridPanel1}.store.reload();" />
                                </Listeners>
                            </ext:ComboBox>
                        </Items>
                        <Plugins>
                            <ext:ProgressBarPager ID="ProgressBarPager1" runat="server" />
                        </Plugins>
                    </ext:PagingToolbar>
                </BottomBar>
            </ext:GridPanel>
        </Items>
    </ext:Window>
    </form>
</body>
note-taking Atenção às linhas em destaque, nestas linhas, estão as definições do “Store”, do “Model” e do “Proxy”.
As definições do modelo estão na tag “<Model>” e dentro de cada tag “<Model>” temos para cada campo a definição “<ModelField>”, onde definimos o nome da propriedade, ou campo, do objeto que será usado no componente “<ColumnModel>” que é definido logo abaixo.
Dentro do “<ColumnModel>” utilizamos a propriedade “DataIndex”, esta tem que ser o mesmo nome definido em “<Model>”, e será este nome que deverá ser usado em seus código javascript.

 

question Mas onde está a definição do <Proxy> dentro do <Store>?

R: Como não definimos um <Proxy> e estamos utilizando uma requisição de página, método “OnReadData=’Store1_ReadData'”, automaticamente será definido como proxy um “PageProxy”.

Code Behind

public partial class GridPanelArray: System.Web.UI.Page
{
	protected void Page_Load(object sender, EventArgs e)
	{
		if(!X.IsAjaxRequest)
		{
			this.BindData();
		}
	}

	protected void Store1_ReadData(object sender, StoreReadDataEventArgs e)
	{
		BindData();
	}

	private void BindData()
	{
		Store store = this.GridPanel1.GetStore();

		store.DataSource = this.Data;
		store.DataBind();
	}

	List<Order> Data
	{
		get
		{
			return Order.Find<Order>();
		}
	}
}

No código acima, temos o método que será chamado quando o “Store” requisitar os dados para o servidor, “Store1_ReadData“. Este método por sua vez chama o método “BindData()“, dentro do método “BindData()”, é carregado uma lista de pedidos (“Order”) e retornados para a propriedade “DataSource” do “Store“, e logo em seguida fazemos a ligação dos dados chamando o método “Store.DataBind()“.

ComboBox

<ext:ComboBox ID="ComboBox4" runat="server" FieldLabel="Selecione vários colaboradores"
	DisplayField="FirstName" Width="320" LabelWidth="130" QueryMode="Local" TypeAhead="true"
	MultiSelect="true">
	<Store>
		<ext:Store ID="Store4" runat="server" Data="<%# ArrayOfEmployees %>" AutoDataBind="true">
			<Model>
				<ext:Model ID="Model4" runat="server">
					<Fields>
						<ext:ModelField Name="EmployeeID" />
						<ext:ModelField Name="FirstName" />
						<ext:ModelField Name="LastName" />
					</Fields>
				</ext:Model>
			</Model>
			<Reader>
				<ext:ArrayReader />
			</Reader>
		</ext:Store>
	</Store>
	<ListConfig>
		<ItemTpl ID="ItemTpl1" runat="server">
			<Html>
				<div class="list-item">
							<h3>ID: {EmployeeID}</h3>
							{FirstName} {LastName}
					</div>
			</Html>
		</ItemTpl>
	</ListConfig>
</ext:ComboBox>

Code Behind

public object ArrayOfEmployees
{
	get
	{
		object result = (from e in Employee.Find<Employee>()
						 select new object[]
						 {
							 e.EmployeeID,
							 e.FirstName,
							 e.LastName,
						 }).ToArray();

		return result;

	}
}

No exemplo do combobox não mudou muita coisa, definimos um “Model” com o mapeamento dos campos, mas  … Temos uma diferença, definimos que o nosso “Reader” irá utilizar uma “ArrayReader”, logo temos que retornar um “Array” de objetos, isto ficou a cargo da propriedade  “ArrayOfEmployees“. A chamada também foi modificada, não usamos o evento “OnDataRead“, e sim a propriedade “Data” com um diretiva ASPX <%# ArrayOfEmployees %>, em ASPX esta diretiva diz ao interpretador para retornar um objeto “datasource“.

Utilizando um DataSource.

O Ext.NET aceita diversos tipos de “DataSources”:

  • LinqDataSource: Aceita comando do tipo “LINQ”;
  • ObjectDataSource: Aceita classes definidas dentro da nossa aplicação. Eu gosto deste 🙂
  • SqlDataSource: Aceita comando do tipo SQL;
  • XmlDataSource: Aceita arquivos do tipo XML. Este datasource requer que seja criado um “Transform” para que possa compreender os dados;
note-taking Você pode usar diversos “datasources” em uma mesma página, para isso basta definir o ID de cada um, e ao utilizar em um “Store“, define o ID do “datasource” na propriedade “Store.DataSourceID“. Cada Store só pode ter um “datasource” definido.
Para saber mais: http://examples.ext.net/#/search/datasource

No exemplo abaixo iremos ver o “ObjectDataSource” em ação.

ASPX Code

<body>
<form id="Form1" runat="server">
<ext:ResourceManager ID="ResourceManager1" runat="server" />
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" SelectMethod="Find" TypeName="Ext.Net.Tutorial.Data.Order" />
<ext:Window ID="Window1" runat="server" Collapsible="true" Maximizable="true" Icon="Money"
	Title="Orders List" Width="1300" Height="600" X="50" Y="50" Layout="Fit" Closable="false">
	<Items>
		<ext:GridPanel ID="GridPanel1" runat="server" Title="Employees" Frame="true" Header="false"
			Border="false">
			<Store>
				<ext:Store ID="Store1" runat="server" DataSourceID="ObjectDataSource1">
					<Model>
						<ext:Model ID="Model1" runat="server">
							<Fields>
								<ext:ModelField Name="CustomerID" />
								<ext:ModelField Name="EmployeeID" />
								<ext:ModelField Name="OrderDate" />
								<ext:ModelField Name="RequiredDate" />
								<ext:ModelField Name="ShippedDate" />
								<ext:ModelField Name="ShipVia" />
								<ext:ModelField Name="Freight" />
								<ext:ModelField Name="ShipName" />
								<ext:ModelField Name="ShipAddress" />
								<ext:ModelField Name="ShipCity" />
								<ext:ModelField Name="ShipRegion" />
								<ext:ModelField Name="ShipPostalCode" />
								<ext:ModelField Name="ShipCountry" />
							</Fields>
						</ext:Model>
					</Model>
				</ext:Store>
			</Store>
			<ColumnModel ID="ColumnModel1" runat="server">
				<Columns>
					<ext:Column ID="Column0" runat="server" Text="CustomerID" Width="150" DataIndex="CustomerID">
					</ext:Column>
					<ext:Column ID="Column1" runat="server" DataIndex="EmployeeID" Text="EmployeeID"
						Width="150" />
					<ext:DateColumn ID="Column2" runat="server" DataIndex="OrderDate" Text="Order Date"
						Width="150" Format="dd/MM/yyyy" />
					<ext:DateColumn ID="DateColumn1" runat="server" DataIndex="RequiredDate" Text="Required Date"
						Width="110" Format="dd/MM/yyyy" />
					<ext:DateColumn ID="DateColumn2" runat="server" DataIndex="ShippedDate" Text="Shipped Date"
						Width="110" Format="dd/MM/yyyy" />
					<ext:Column ID="Column3" runat="server" DataIndex="ShipVia" Text="Ship Via" Width="150" />
					<ext:Column ID="Column4" runat="server" DataIndex="Freight" Text="Freight" Width="100" />
				</Columns>
			</ColumnModel>
			<View>
				<ext:GridView ID="GridView1" runat="server">
					<GetRowClass Handler="return 'x-grid-row-expanded';" />
				</ext:GridView>
			</View>
			<SelectionModel>
				<ext:RowSelectionModel ID="RowSelectionModel1" runat="server" Mode="Single" />
			</SelectionModel>
			<Features>
				<ext:RowBody ID="RowBody1" runat="server">
					<GetAdditionalData Handler="orig.rowBody = '<div><span style=\'color: red\'>Ship Information</span>' + '</br/>' + data.ShipName + '</br/>' + data.ShipAddress + '</br/>' + data.ShipCity + (data.ShipRegion == null ? '' : ' - ' + data.ShipRegion) + '</br/>' + (data.ShipPostalCode == null ? '' : data.ShipPostalCode + '</br/>') + data.ShipCountry + '</div>'; orig.rowBodyColspan = record.fields.getCount();" />
				</ext:RowBody>
			</Features>
		</ext:GridPanel>
	</Items>
</ext:Window>
</form>
</body>

No código em destaque, acima, definimos o “ObjectDataSource” para o nosso “Store“, vamos explicar algumas propriedades:

  • ID: “ObjectDataSource1”: Define o nome do objeto “DataSource” que deverá ser utilizado no “Store“;
  • TypeName: “Ext.Net.Tutorial.Data.Order”: Define o nome do objeto que será instanciado pelo “DataSource”;
  • SelectMethod: “Find”: Nome do método que será chamado para popular o “DataSource”;

Nas outras linhas em destaque, como sempre, definimos o nosso “Model” e já estamos familiarizados com ele.

Utilizando um Proxy

Até que enfim… Falamos tanto do acesso aos dados, de diversas formas, que já estava esquecendo deste tal “proxy“. Vamos ver como trabalhar com o proxy. Para isso, iremos criar um “ASPX Hanlder“.

question Handler? O que é isso?

R: Os Handlers, são manipuladores responsáveis por tratar as requisições HTTP, não têm interface com usuário e são executados de forma síncrona. Devem implementar a interface “System.Web.IHttpHandler“. Um exemplo de requisição que implementa a interface “System.Web.IHttpHandler” é a “System.Web.UI.Page“, que deve ser herdada para criar os formulários em ASPX. (Web Forms). Esta é quem define toda a interatividade com o usuário. OS “Handlers” são úteis quando precisamos de acessar dados do servidor, sem a intervenção do usuário.

Preparando o ambiente.

Antes de começarmos, iremos criar 3 “ASPX Handlers”:

  1. HandlerBase<T>: Abstração para o tratamento dos dados, pois o código de tratamento é semelhante para todos os outros “handlers” que tratam o acesso aos dados.
  2. EmployeeHandler: É o “handler” responsável por exibir os dados dos colaboradores;
  3. OrderHandler: É o “handler” responsável por exibir os dados dos pedidos;

Abstração HandlerBase

public abstract class HandlerBase<T>: System.Web.IHttpHandler
	where T: IModelBase, new()
{
	public void ProcessRequest(HttpContext context)
	{
		//-------------------------------------------------------------------------
		// Definir o tipo de retorno da resposta da requisição.
		// Iremos retornar um objeto do tipo JSON
		//-------------------------------------------------------------------------
		context.Response.ContentType = "application/json";

		//-------------------------------------------------------------------------
		// Recuperar os parâmetros que foram passados pela requisição
		//-------------------------------------------------------------------------
		StoreRequestParameters storeParams = new StoreRequestParameters(context);

		//-------------------------------------------------------------------------
		// Paginar
		//-------------------------------------------------------------------------
		Paging<T> data = DataPaging(storeParams.Start, storeParams.Limit,
			storeParams.Sort.Count() > 0 ? storeParams.Sort[0].Property : "",
			storeParams.Sort.Count() > 0 ? storeParams.Sort[0].Direction.ToString() : "",
			storeParams.GridFilters);
		context.Response.Write(JSON.Serialize(data));
	}

	public bool IsReusable
	{
		get
		{
			return false;
		}
	}

	public static Paging<T> DataPaging(int start, int limit, string sort, string dir, FilterConditions fc)
	{
		List<T> data = DbContext.Find<T>(new T());

		#region Filtrar
		//-------------------------------------------------------------------------
		// Se foi definido alguma condição, temos que filtrar os registros
		//-------------------------------------------------------------------------
		if(fc != null && fc.Conditions.Count > 0)
		{
			foreach(FilterCondition condition in fc.Conditions)
			{
				Comparison comparison = condition.Comparison;
				string field = condition.Field;
				FilterType type = condition.Type;

				object value;
				switch(condition.Type)
				{
					case FilterType.Boolean:
						value = condition.Value<bool>();
						break;
					case FilterType.Date:
						value = condition.Value<DateTime>();
						break;
					case FilterType.List:
						value = condition.List;
						break;
					case FilterType.Numeric:
						if(data.Count > 0 && data[0].GetType().GetProperty(field).PropertyType == typeof(int))
						{
							value = condition.Value<int>();
						}
						else
						{
							value = condition.Value<double>();
						}

						break;
					case FilterType.String:
						value = condition.Value<string>();
						break;
					default:
						throw new ArgumentOutOfRangeException();
				}

				//-------------------------------------------------------------------------
				// Aqui removemos todos os registros que não satisfazem a condição
				//-------------------------------------------------------------------------
				data.RemoveAll(
					item =>
					{
						object oValue = item.GetType().GetProperty(field).GetValue(item, null);
						IComparable cItem = oValue as IComparable;

						switch(comparison)
						{
							case Comparison.Eq:

								switch(type)
								{
									case FilterType.List:
										return !(value as List<string>).Contains(oValue.ToString());
									case FilterType.String:
										return !oValue.ToString().ToLower().Contains(value.ToString().ToLower());
									default:
										return !cItem.Equals(value);
								}

							case Comparison.Gt:
								return cItem.CompareTo(value) < 1;
							case Comparison.Lt:
								return cItem.CompareTo(value) > -1;
							default:
								throw new ArgumentOutOfRangeException();
						}
					}
				);
			}

		}
		#endregion

		#region Ordenar
		//-------------------------------------------------------------------------
		// Ordena os registros de acordo com o que foi passado pelo usuário
		//-------------------------------------------------------------------------
		if(!string.IsNullOrEmpty(sort))
		{
			data.Sort(delegate(T x, T y)
			{
				object a;
				object b;

				int direction = dir == "DESC" ? -1 : 1;

				a = x.GetType().GetProperty(sort).GetValue(x, null);
				b = y.GetType().GetProperty(sort).GetValue(y, null);

				return CaseInsensitiveComparer.Default.Compare(a, b) * direction;
			});
		}
		#endregion

		#region Paginar
		//-------------------------------------------------------------------------
		// Faz a paginação do registro.
		//-------------------------------------------------------------------------
		if((start + limit) > data.Count)
		{
			limit = data.Count - start;
		}

		List<T> rangeData = (start < 0 || limit < 0) ? data : data.GetRange(start, limit);
		#endregion

		//retornar
		return new Paging<T>(rangeData, data.Count);
	}
}

Exemplos de acesso

Classe concreta EmployeeHandler

/// <summary>
/// Summary description for EmployeeHandler
/// </summary>
public class EmployeeHandler: HandlerBase<Employee>
{
}

Classe concreta OrderHandler

/// <summary>
/// Summary description for GridPanelHandler
/// </summary>
public class OrderHandler: HandlerBase<Order>
{
}

Estamos quase lá… Vamos ver agora dois exemplos, uma “Combobox” e uma “GridPanel” acessando os dados por um “proxy” e utilizando um “handler” para tratar os dados.

ASPX Code

<ext:ComboBox ID="ComboBox5" runat="server" FieldLabel="Selecione vários colaboradores"
	DisplayField="FirstName" Width="320" LabelWidth="130" QueryMode="Local" TypeAhead="true"
	MultiSelect="true">
	<Store>
		<ext:Store ID="Store5" runat="server" AutoDataBind="true">
			<Proxy>
				<ext:AjaxProxy Url="../DataHandler/EmployeeHandler.ashx">
					<ActionMethods Read="GET" />
					<Reader>
						<ext:JsonReader Root="data" TotalProperty="total" />
					</Reader>
				</ext:AjaxProxy>
			</Proxy>
			<Model>
				<ext:Model ID="Model5" runat="server">
					<Fields>
						<ext:ModelField Name="EmployeeID" />
						<ext:ModelField Name="FirstName" />
						<ext:ModelField Name="LastName" />
					</Fields>
				</ext:Model>
			</Model>
		</ext:Store>
	</Store>
	<ListConfig>
		<ItemTpl ID="ItemTpl2" runat="server">
			<Html>
				<div class="list-item">
							<h3>ID: {EmployeeID}</h3>
							{FirstName} {LastName}
					</div>
			</Html>
		</ItemTpl>
	</ListConfig>
</ext:ComboBox>

GridPanel

ASPX Code

<head id="Head1" runat="server">
    <title>Tutorial Ext.NET</title>
    <link href="../css/gridpanel.css" rel="stylesheet" type="text/css" />
    <script type="text/javascript" src="../Scripts/GridPanel.js"></script>
</head>
<body>
    <form id="Form1" runat="server">
    <ext:ResourceManager ID="ResourceManager1" runat="server" />
    <ext:Window ID="Window1" runat="server" Collapsible="true" Maximizable="true" Icon="Money"
        Title="Orders List" Width="1300" Height="600" X="50" Y="50" Layout="Fit" Closable="false">
        <Items>
            <ext:GridPanel ID="GridPanel1" runat="server" Title="Employees" Frame="true" Header="false"
                Border="false">
                <Store>
                    <ext:Store ID="Store1" runat="server" PageSize="10">
                        <Proxy>
                            <ext:AjaxProxy Url="../DataHandler/OrderHandler.ashx">
                                <ActionMethods Read="GET" />
                                <Reader>
                                    <ext:JsonReader Root="data" TotalProperty="total" />
                                </Reader>
                            </ext:AjaxProxy>
                        </Proxy>
                        <Model>
                            <ext:Model ID="Model1" runat="server">
                                <Fields>
                                    <ext:ModelField Name="CustomerID" />
                                    <ext:ModelField Name="EmployeeID" />
                                    <ext:ModelField Name="OrderDate" />
                                    <ext:ModelField Name="RequiredDate" />
                                    <ext:ModelField Name="ShippedDate" />
                                    <ext:ModelField Name="ShipVia" />
                                    <ext:ModelField Name="Freight" />
                                    <ext:ModelField Name="ShipName" />
                                    <ext:ModelField Name="ShipAddress" />
                                    <ext:ModelField Name="ShipCity" />
                                    <ext:ModelField Name="ShipRegion" />
                                    <ext:ModelField Name="ShipPostalCode" />
                                    <ext:ModelField Name="ShipCountry" />
                                </Fields>
                            </ext:Model>
                        </Model>
                        <Sorters>
                            <ext:DataSorter Property="CustomerID" Direction="ASC" />
                        </Sorters>
                    </ext:Store>
                </Store>
                <ColumnModel ID="ColumnModel1" runat="server">
                    <Columns>
                        <ext:Column ID="Column0" runat="server" Text="CustomerID" Width="150" DataIndex="CustomerID">
                        </ext:Column>
                        <ext:Column ID="Column1" runat="server" DataIndex="EmployeeID" Text="EmployeeID"
                            Width="150" />
                        <ext:DateColumn ID="Column2" runat="server" DataIndex="OrderDate" Text="Order Date"
                            Width="150" Format="dd/MM/yyyy" />
                        <ext:DateColumn ID="DateColumn1" runat="server" DataIndex="RequiredDate" Text="Required Date"
                            Width="110" Format="dd/MM/yyyy" />
                        <ext:DateColumn ID="DateColumn2" runat="server" DataIndex="ShippedDate" Text="Shipped Date"
                            Width="110" Format="dd/MM/yyyy" />
                        <ext:Column ID="Column3" runat="server" DataIndex="ShipVia" Text="Ship Via" Width="150" />
                        <ext:Column ID="Column4" runat="server" DataIndex="Freight" Text="Freight" Width="100" />
                    </Columns>
                </ColumnModel>
                <View>
                    <ext:GridView ID="GridView1" runat="server">
                        <GetRowClass Handler="return 'x-grid-row-expanded';" />
                    </ext:GridView>
                </View>
                <SelectionModel>
                    <ext:RowSelectionModel ID="RowSelectionModel1" runat="server" Mode="Single" />
                </SelectionModel>
                <Features>
                    <ext:RowBody ID="RowBody1" runat="server">
                        <GetAdditionalData Handler="orig.rowBody = '<div><span style=\'color: red\'>Ship Information</span>' + '</br/>' + data.ShipName + '</br/>' + data.ShipAddress + '</br/>' + data.ShipCity + (data.ShipRegion == null ? '' : ' - ' + data.ShipRegion) + '</br/>' + (data.ShipPostalCode == null ? '' : data.ShipPostalCode + '</br/>') + data.ShipCountry + '</div>'; orig.rowBodyColspan = record.fields.getCount();" />
                    </ext:RowBody>
                </Features>
                <BottomBar>
                    <ext:PagingToolbar ID="PagingToolbar1" runat="server">
                        <Items>
                            <ext:Label ID="Label1" runat="server" Text="Page size:" />
                            <ext:ToolbarSpacer ID="ToolbarSpacer1" runat="server" Width="10" />
                            <ext:ComboBox ID="ComboBox1" runat="server" Width="80">
                                <Items>
                                    <ext:ListItem Text="1" />
                                    <ext:ListItem Text="2" />
                                    <ext:ListItem Text="10" />
                                    <ext:ListItem Text="20" />
                                    <ext:ListItem Text="40" />
                                    <ext:ListItem Text="100" />
                                </Items>
                                <SelectedItems>
                                    <ext:ListItem Value="10" />
                                </SelectedItems>
                                <Listeners>
                                    <Select Handler="#{GridPanel1}.store.pageSize = parseInt(this.getValue(), 10); #{GridPanel1}.store.reload();" />
                                </Listeners>
                            </ext:ComboBox>
                        </Items>
                        <Plugins>
                            <ext:ProgressBarPager ID="ProgressBarPager1" runat="server" />
                        </Plugins>
                    </ext:PagingToolbar>
                </BottomBar>
                <Features>
                    <ext:GridFilters ID="GridFilters1" runat="server">
                        <Filters>
                            <ext:StringFilter DataIndex="CustomerID" />
                            <ext:NumericFilter DataIndex="EmployeeID" />
                            <ext:DateFilter DataIndex="OrderDate">
                                <DatePickerOptions runat="server" TodayText="Now" />
                            </ext:DateFilter>
                            <ext:DateFilter DataIndex="RequiredDate">
                                <DatePickerOptions runat="server" TodayText="Now" />
                            </ext:DateFilter>
                            <ext:DateFilter DataIndex="ShippedDate">
                                <DatePickerOptions runat="server" TodayText="Now" />
                            </ext:DateFilter>
                            <ext:NumericFilter DataIndex="ShipVia" />
                            <ext:NumericFilter DataIndex="Freight" />
                        </Filters>
                    </ext:GridFilters>
                </Features>
            </ext:GridPanel>
        </Items>
    </ext:Window>
    </form>
</body>
</html>

Vejam nos códigos em destaque a definição do proxy, vamos agora explicar o que é cada propriedade que foi utilizada.

  • <Proxy>“: Usamos esta marcação para definir que neste “Store” será usado um “proxy“.
  • <ext:AjaxProxy Url=”../DataHandler/OrderHandler.ashx”>“: Esta marcação define o tipo de “proxy” que iremos usar. Eu utilizo, na maioria das vezes, o “AjaxProxy“, sempre me atendeu. Na definição desta marcação temos a propriedade  “Url” e como podem ver, ela define o caminho do nosso “handler” em relação à página;
  • <ActionMethods Read=”GET” />“: Aqui definimos a ação da requisição como um “GET“, pois vamos solicitar uma informação;
  • <ext:JsonReader Root=”data” TotalProperty=”total” />“: Nesta marcação, definimos o nosso “Reader“, nosso leitor da informação, como eu defini que meu retorno no “handler” será um “JSON” .
    • Lembram da definição do “handler“? context.Response.ContentType = “application/json;

    • Esta marcação tem duas propriedades importantes:
      • Root“, que define o nome da marcação pai de todas, no objeto “JSON” retornado e;
      • TotalProperty“, que define o nome da propriedade que representa o total de registros retornados pela aplicação. Veja abaixo um retorno para facilitar o entendimento destas duas propriedades
        {
         "data": [{
         "ID": 1,
         "EmployeeID": 1,
         "LastName": "Davolio",
         "FirstName": "Nancy",
         "Title": "Sales Representative",
         "TitleOfCourtesy": "Ms.",
         "BirthDate": "1948-12-08T00:00:00",
         "HireDate": "1992-05-01T00:00:00",
         "Address": "507 - 20th Ave. E.Apt. 2A",
         "City": "Seattle",
         "Region": "WA",
         "PostalCode": "98122",
         "Country": "USA",
         "HomePhone": "(206) 555-9857",
         "Extension": "5467",
         "Photo": "System.Byte[]",
         "Notes": "Education includes a BA in psychology from Colorado State University in 1970. 
                   She also completed \"The Art of the Cold Call.\" 
                   Nancy is a member of Toastmasters International.",
         "ReportsTo": 2,
         "PhotoPath": "http://accweb/emmployees/davolio.bmp",
         "New": false
         }],
         "total": 1
        }
whew Ufa! Este tópico foi demorado, mas valeu a pena. Aprendemos nele como acessar dados utilizando “Store“, ‘Models“… Definimos “DataSources“, “Proxies” entre outras funcionalidades.E ainda de quebra conhecemos dois componentes, “ComboBox” e “GridPanel“.

É isso ai pessoal 🙂
Até o próximo
♦ Marcelo

About Marcelo

Nascido em Juruaia/MG em uma fazenda de criação de búfalos, e residindo na região Sul do Brasil. Trabalha com desenvolvimento de aplicações desde os 17 anos. Atualmente é Arquiteto Organizacional na Unimake Software. Para saber mais ... http://desenvolvedores.net/marcelo []'s

Criando métodos no lado Cliente/Servidor (DirectMethod)

0
1 Estrela2 Estrelas3 Estrelas4 Estrelas5 Estrelas (Sem votação.)
Loading...
18 de junho de 2014

DirectMethod

Para um melhor entendimento deste Artigo veja o Índice (Rich Internet Applications com Ext.Net)

Criando métodos no lado Cliente/Servidor (DirectMethod)

Como vimos, os DirectEvents são extremamente poderosos, são inseridos no controle, lançados no lado cliente e executados no lado servidor.

Com todo este poder de trabalhar do lado cliente integrando ao lado servidor, temos os “DirectMethods“.

note-taking Logo, podemos definir que “DirectMethods” são métodos em .NET definidos no servidor que podemos chama-los no lado cliente pelo javascript.

Vejamos dois exemplos:

Chamando pelo Handler

ASPX Code

<ext:Button ID="Button1" runat="server" Text="Que horas são?" Icon="Clock">
	<Listeners>
		<Click Handler="App.direct.ShowServerTime();">
		</Click>
	</Listeners>
</ext:Button>

Code Behind

[DirectMethod]
public void ShowServerTime()
{
	X.MessageBox.Show(new MessageBoxConfig
	{
		Buttons = MessageBox.Button.OK,
		Title = "Server Time",
		Message = "No servidor são exatamente " + DateTime.Now.ToLongTimeString(),
		Icon = MessageBox.Icon.INFO
	});
}

Chamando pelo ponteiro (Fn)

ASPX Code

<script type="text/javascript" src="../Scripts/DirectMethods.js"></script>
<ext:Button ID="Button2" runat="server" Text="Que horas são?" Icon="Clock">
	<Listeners>
		<Click Fn="MyApp.showTime">
		</Click>
	</Listeners>
</ext:Button>

Crie um arquivo javascript e cole seguinte código no seu arquivo:

/**
<pre> Arquivo de definição dos objetos em javascript
 */

// definir o namespace de base para a aplicação
var MyApp = {};

MyApp.showTime = function() {
    App.direct.ShowServerTime();
};

Vamos analisar agora os pontos importantes do código que foi montado.
Primeiro vamos pegar a definição do método “public void ShowServerTime()” nele temos a definição do atributo  “[DirectMethod]“.

O atributo “DirectMethod” diz ao Ext.NET para criar um proxy entre o cliente e o servidor, automaticamente será criado um código javascript e o nosso método será exposto como um método  javascript.

note-taking Os métodos do tipo “DirectMethod“, diferentes dos tipos ASPX, não precisam ser estáticos, podem ser definidos como não-estáticos e no escopo do objeto.
Se definidos de forma estática, a vantagem é que o ciclo de vida da página ASP não ocorre, mas, em contrapartida, você não pode acessar os controles.

O namespace padrão criado para separar os “DirectMethods” no Ext.NET é o “App.direct“, neste namespace estão todos os “DirectMethods” criados para a requisição. Como pode ser visto na linha em destaque no código javascript.

Perceba que nos dois exemplos, temos a chamada pelo manipulador, “handler” e pela função “Fn“. Ambos já foram explicados no tópico “Utilizando Listeners (Eventos no lado cliente) e DirectEvents (Lado Servidor)“.

Retornando valores

Muitas vezes precisamos ir ao servidor e retornar algum resultado para o lado cliente, isso é possível, pois podemos ter retornos em nossos “DirectMethods“. Vamos aos exemplos:

Com um resultado simples

ASPX Code

<script type="text/javascript" src="../Scripts/DirectMethods.js"></script>
<ext:Button ID="Button3" runat="server" Text="Que horas são?" Icon="Clock">
	<Listeners>
		<Click Fn="MyApp.showTimeResult">
		</Click>
	</Listeners>
</ext:Button>

Code Behind

[DirectMethod]
public string ShowTimeResult()
{
	return "No servidor são exatamente " + DateTime.Now.ToLongTimeString();
}

javascript

MyApp.showTimeResult = function() {
    App.direct.ShowTimeResult({
        success : function(result) {
            Ext.Msg.show({
                title : 'Server Time',
                msg : result,
                buttons : Ext.Msg.OK,
                icon : Ext.Msg.WARNING
            });
        }
    });
};

O retorno não se restringe à apenas os tipos primários, podemos ter o nosso próprio tipo para retornar. Veja o exemplo:

Com um tipo definido

Definindo o nosso tipo

public struct MyInfo
{
	public string Nome { get; set; }
	public string Email { get; set; }
}

ASPX Code

<script type="text/javascript" src="../Scripts/DirectMethods.js"></script>
<ext:Button ID="Button4" runat="server" Text="Quem é você?" Icon="Information">
	<Listeners>
		<Click Fn="MyApp.getInfo">
		</Click>
	</Listeners>
</ext:Button>

Code Behind

[DirectMethod]
public MyInfo GetInfo()
{
	return new MyInfo {
		Nome = "http://desenvolvedores.net",
		Email="aha@evitandospam.com :)"
	};
}

javascript

MyApp.getInfo = function() {
    App.direct.GetInfo({
        success : function(result) {
            var msg = 'Eu sou o site ' + result.Nome + ' e meu e-mail é ' + result.Email;

            Ext.Msg.show({
                title : 'Info',
                msg : msg,
                buttons : Ext.Msg.OK,
                icon : Ext.Msg.INFO
            });
        }
    });
};

DirectMethods com parâmetros

Tipos primitivos

Podemos passar parâmetros para os nossos “DirectMethods“. No exemplo abaixo, eu vou utilizar um parâmetro do tipo data, calcular a idade e retornar a idade que deverá ser exibida ao utilizador da aplicação.

ASPX Code

<ext:DateField ID="DateField1" runat="server" FieldLabel="Data de Nascimento">
</ext:DateField>
<script type="text/javascript" src="../Scripts/DirectMethods.js"></script>
<ext:Button ID="Button5" runat="server" Text="Qual a sua idade?" Icon="Information">
	<Listeners>
		<Click Handler="MyApp.getAge(#{DateField1}.value);">
		</Click>
	</Listeners>
</ext:Button>
note-taking Atenção à linha em destaque, como utilizamos a passagem de parâmetro direto pelo evento, temos que definir a chamada como um “handler” , pois caso contrário o nosso método será chamado durante o carregamento da página. E não queremos isso. 🙂

Code Behind

[DirectMethod]
public int GetAge(DateTime dob)
{
	int age = DateTime.Now.Year - dob.Year;
	if(DateTime.Now.Month < dob.Month ||
		(DateTime.Now.Month == dob.Month &&
		DateTime.Now.Day < dob.Day))
		age--;

	return age;
}

javascript

MyApp.getAge = function(dob) {
    App.direct.GetAge(dob, {
        success : function(result) {
            var msg = 'Olá! Você tem ' + result + ' anos.';

            Ext.Msg.show({
                title : 'Age',
                msg : msg,
                buttons : Ext.Msg.OK,
                icon : Ext.Msg.INFO
            });
        }
    });
};

Parâmetros com tipos definidos

Se existir a necessidade de se passar um tipo definido como parâmetro, isso também é possível. Veja o exemplo:

ASPX Code

<ext:TextField ID="TextField1" runat="server" FieldLabel="Informe seu nome">
</ext:TextField>
<ext:TextField ID="TextField2" runat="server" FieldLabel="Informe seu email">
</ext:TextField>
<ext:Button ID="Button6" runat="server" Icon="Accept" Text="Enviar">
	<Listeners>
		<Click Handler="MyApp.showInfo(#{TextField1}.value, #{TextField2}.value);">
		</Click>
	</Listeners>
</ext:Button>

Code Behind

[DirectMethod]
public string ShowInfo(MyInfo info)
{
	return String.Format("Olá {0}! Seu e-mail é {1}.", info.Nome, info.Email);
}

javascript

MyApp.showInfo = function(nome, email) {
    /*
     * Devemos criar aqui o nosso objeto que será passado como parâmetro.
     * Temos que respoeitar exatamente a definição de nome que demos à nossas
     * propriedades.
     * Os nomes das propriedades do objeto criado não são sensíveis ao caso.
     */
    var myinfo = {
        nome : nome,
        email : email
    };

    App.direct.ShowInfo(myinfo, {
        success : function(result) {
            Ext.Msg.show({
                title : 'Info',
                msg : result,
                buttons : Ext.Msg.OK,
                icon : Ext.Msg.INFO
            });
        }
    });
};

Tratando exceções

Como nossos sitema não é a prova de falhas, temos que tratar as exceções que podem ocorrer no processamento de uma requisição ao servidor.
Nos exemplos abaixo, iremos ver as exceções tratadas e as não tratadas.

Uma exceção tratada

ASPX Code

<ext:Button ID="Button7" runat="server" Icon="Error" Text="Erro :(">
	<Listeners>
		<Click Fn="MyApp.catchException">
		</Click>
	</Listeners>
</ext:Button>

Code Behind

[DirectMethod]
public void CatchException()
{
	try
	{
		throw new Exception("Ops! Um erro foi lançado.");
	}
	catch(Exception ex)
	{
		Ext.Net.ResourceManager.AjaxSuccess = false;
		Ext.Net.ResourceManager.AjaxErrorMessage = ex.Message;
	}
}
note-taking As duas linhas em destaque indicam que o retorno deverá acontecer como uma falha, logo devemos dizer ao Ext.NET que houve um erro na requisição. Para isso passamos a propriedade “Ext.Net.ResourceManager.AjaxSuccess” para false e a propriedade “Ext.Net.ResourceManager.AjaxErrorMessage” irá receber a mensagem de erro que será exibida ao utilizador da aplicação.

javascript

MyApp.catchException = function() {
    App.direct.CatchException({
        failure : function(result) {
            Ext.Msg.alert('Erro!', result);
        }
    });
};
note-taking Neste ponto, temos que tratar um método diferente,no caso o método “failure‘ que será chamado sempre que ocorrer um erro no lado servidor, ou sempre que a propriedade “Ext.Net.ResourceManager.AjaxSuccess” retornar “false“.

Uma exceção não tratada

ASPX Code

<ext:Button ID="Button8" runat="server" Icon="Error" Text="Erro não tratado :(">
	<Listeners>
		<Click Fn="MyApp.unhandledException">
		</Click>
	</Listeners>
</ext:Button>

Code Behind

[DirectMethod]
public void UnhandledException()
{
	throw new Exception("Ops! Um erro não tratado foi lançado.");
}

javascript

MyApp.unhandledException = function() {
App.direct.UnhandledException({
	failure : function(result) {
		Ext.Msg.alert('Erro!', result);
	}
});
}; 

Uma exceção não tratada e sem “failure”

<ext:Button ID="Button9" runat="server" Icon="Error" Text="Erro não tratado e sem failure :(">
	<Listeners>
		<Click Handler="App.direct.UnhandledException();">
		</Click>
	</Listeners>
</ext:Button>

Como podemos perceber, é sempre bom termos o hábito de tratar a falha em uma requisição “DirectMethod“, nunca sabemos se ela irá acontecer ou não devido aos diversos fatores quando desenvolvemos para web.

Se o método “failure” não for especificado, uma caixa de mensagem semelhante à esta será exibida.

request_failure

Mais sobre “DirectMethods”

Como vimos, o namespace padrão definido pelo Ext.NET é o “App.direct”, mas, podemos decidir que queremos utilizar o nosso próprio namespace, para isso temos que modificar a definição do nosso atributo “[DirectMethod]“, vejamos algumas propriedades do mesmo.

Definindo um namespace (CompanyX)

ASPX Code

<ext:Button ID="Button10" runat="server" Icon="ApplicationGo" Text="Namespace Defin">
	<Listeners>
		<Click Fn="MyApp.definedNamespace">
		</Click>
	</Listeners>
</ext:Button>

Code Behind

[DirectMethod(Namespace = "CompanyX")]
public void DefinedNamespace()
{
	X.MessageBox.Show(new MessageBoxConfig
	{
		Buttons = MessageBox.Button.OK,
		Title = "Olá!",
		Message = "Eu fui chamado de um namespace definido por você. :)",
		Icon = MessageBox.Icon.INFO
	});
}
note-taking Perceba que na linha em destaque, nós adicionamos o namespace que queremos para a nossa aplicação, neste caso “CompanyX

javascript

MyApp.definedNamespace = function() {
    CompanyX.DefinedNamespace();
};
question Isto é útil quando temos uma aplicação muito grande, e queremos definir o nosso próprio namespace.
Mas, eu tenho que ficar a todo momento definindo o namespace para um “DirectMethod“?

R: Não.
Você não precisa a todo momento ficar escrevendo o namespace padrão.
Dentro do web.config, nas configurações do ext.net você pode adicionar a seguinte propriedade

<extnet directMethodNamespace="CompanyX"/>

e voilà todos os seus “DirectMethods” estarão utilizando o namespace padrão “CompanyX“.

 

note-taking Mas tome o cuidado de corrigir todos os pontos que você usou o namespace antigo “App.direct“, pois eles deixarão de funcionar.

Para saber mais sobre “DirectMethods” veja os exemplo em http://examples.ext.net/#/Events/DirectMethods/Overview/


É isso ai pessoal 🙂
Até o próximo
♦ Marcelo

About Marcelo

Nascido em Juruaia/MG em uma fazenda de criação de búfalos, e residindo na região Sul do Brasil. Trabalha com desenvolvimento de aplicações desde os 17 anos. Atualmente é Arquiteto Organizacional na Unimake Software. Para saber mais ... http://desenvolvedores.net/marcelo []'s

Listeners_Eventos

Para um melhor entendimento deste Artigo veja o Índice (Rich Internet Applications com Ext.Net)

Listeners – Eventos lado cliente

Para facilitar o entendimento do artigo, vamos primeiro definir o que são listeners e eventos.

Listeners

Listeners são objetos ouvintes que notificam outros objetos quando acontece alguma mudança no comportamento do objeto. Os listeners utilizam o Design Pattern Observer.

Eventos

Os eventos são os tipos mais comuns que utilizam um listener, pois são neles que definimos quais métodos  serão chamados (Alguém disse delegates?) quando uma ação ocorrer.

Eventos são lançados sempre que uma ação acontece no nosso objeto, seja motivada pelo usuário, pelo próprio sistema ou por mensagens do sistema operacional.

Eventos no Ext.NET

O Ext.NET suporta eventos do lado cliente, AJAX e as formas tradicionais de tratamento de eventos.
Cada componente tem seus eventos, lado cliente, definidos dentro da tag “listeners“.

Como exemplo, vamos pegar um simples clique de botão e exibir uma mensagem.

<body>
    <ext:ResourceManager ID="ResourceManager1" runat="server">
    </ext:ResourceManager>
    <form id="form1" runat="server">
    <ext:Button ID="Button1" runat="server" Icon="Accept" Text="Clica em mim.">
        <Listeners>
            <Click Handler="Ext.Msg.show({
                                                   title: 'Oi!',
                                                   msg: 'Oba! Fui clicado.',
                                                   buttons: Ext.Msg.OK,
                                                   icon: Ext.Msg.WARNING
                                               });">
            </Click>
        </Listeners>
    </ext:Button>
    </form>
</body>

Neste pequeno trecho de código podemos dizer que tratamos o evento clique de um botão qualquer.

Uma outra forma de tratarmos o evento, seria criando uma função em javascript.

note-taking Eu, particularmente, prefiro esta segunda abordagem, pois assim deixo todos os meus fontes separados entre as classes (.cs), o código html (.aspx) e o código de script (.js).

Para este exemplo, crie um arquivo com o nome de “MyApp.js” no seu projeto e cole o código abaixo:

/**
 Arquivo de definição dos objetos em javascript
 */

// definir o namespace de base para a aplicação
var MyApp = {};

/**
 Define o método listener para o clique do botão
 */
MyApp.Button1Click = function() {
    Ext.Msg.show({
        title : 'Oi!',
        msg : 'Oba! Fui clicado em uma função.',
        buttons : Ext.Msg.OK,
        icon : Ext.Msg.WARNING
    });
};

Agora para a chamada do método clique, iremos definir um ponteiro para uma função em javascript.

<ext:Button ID="Button2" runat="server" Icon="Accept" Text="Clica em mim.">
        <Listeners>
            <Click Fn="MyApp.Button1Click">
            </Click>
        </Listeners>
</ext:Button>

Neste ponto podemos perceber que temos dois meios de chamar um método para o evento “Click“, no primeiro utilizamos um manipulador (handler) diretamente na definição do evento. E no segundo método, definimos um ponteiro (pointer) para uma função definida em nosso arquivo de script.

Esta é a diferença entre usar “Handler” e “Fn“.

Se o evento só deve ser executado uma única vez, podemos definir a propriedade “Single” para “true” e o evento só será executado uma vez, até que a página seja recarregada.

Se for necessário definir o escopo em que o evento deverá ser executado, devemos utilizar a propriedade “scope” e passar o nome do objeto contêiner onde o evento será executado. Se nada for informado, será utilizado o escopo global.

Determinando os argumentos do evento

Usando a documentação do Ext JS você poderá encontrar os argumentos que são passados para cada evento. Tenha em mente que diferentes controles têm diferentes eventos e assinaturas de evento.

Para uma documentação completa do lado cliente veja o link http://docs-origin.sencha.com/extjs/4.1.3.

DirectEvents (Eventos do Lado Servidor)

Os “DirectEvents” são eventos que são lançados pelo lado cliente e que podem ser capturados pelo lado servidor.

Tenha em mente que quando um “DirectEvent” é lançado, todo o controle deverá ser reconstruído do lado servidor, como é feito em um post tradicional. Se o seu form tem o atributo “runat=’server’” todo o formulário será reenviado por padrão.

Isto pode nos causar alguns problemas e para validar se devemos recriar e evitar vários códigos de inicialização da nossa página, podemos usar a propriedade “X.IsAjaxRequest”,  se true, a requisição veio de um evento AJAX.  Esta propriedade funciona como a propriedade do ASP.NET “IsPostBack“.

Vamos ver 3 exemplos de chamadas “DirectEvents” que são usados com muito frequência.

Sem parâmetros

ASPX Code

<ext:Button ID="Button1" runat="server" Icon="Clock" Text="Que horas são?"><DirectEvents>
            <Click OnEvent="Button1Click">
            </Click>
        </DirectEvents>
    </ext:Button>

Code Behind

protected void Button1Click(object sender, DirectEventArgs e)
        {
            X.Msg.Show(new MessageBoxConfig
            {
                Buttons = MessageBox.Button.OK,
                Title = "Olá!",
                Message = String.Format("São {0}.", DateTime.Now.ToLongTimeString())
            });
        }

Com parâmetros

Para definir parâmetros, temos que utilizar a marcação “ExtraParams” que possuem 3 propriedades importantes:

  1. Mode:
    1. Pode ser “Raw“: Define que o conteúdo da propriedade  “Value” deverá ser executado antes de ser enviado ao servidor;
    2. Poder ser “Value“: Define que o conteúdo da propriedade “Value” deverá ser retornado como especificado;
  2. Name: Nome do parâmetro que será enviado ao servidor, pode ser passado mais de um item de parâmetro, este nome será o índice que iremos recuperar o valor do parâmetro no servidor;
  3. Value:  Valor que deverá ser passado ao servidor, atente para o item 1. Mode, que define como este valor deverá ser interpretado;

ASPX Code

<ext:TextField ID="TextField1" runat="server" FieldLabel="Informe seu nome.">
    </ext:TextField>
    <ext:Button ID="Button2" runat="server" Icon="Clock" Text="Que horas são?">
        <DirectEvents>
            <Click OnEvent="Button2Click">
                <ExtraParams>
                    <ext:Parameter Mode="Raw" Value="#{TextField1}.value" Name="nome">
                    </ext:Parameter>
                </ExtraParams>
            </Click>
        </DirectEvents>
    </ext:Button>

Code Behind

protected void Button2Click(object sender, DirectEventArgs e)
        {
            //Perceba que pegamos aqui pelo nome do extra params que informamos no ASPX Code
            string nome = e.ExtraParams["nome"];

            X.Msg.Show(new MessageBoxConfig
            {
                Buttons = MessageBox.Button.OK,
                Title = String.Format("Olá {0}!", nome),
                Message = String.Format("São {0}.", DateTime.Now.ToLongTimeString())
            });
        }

Com parâmetros de um objeto definido

Com esta opção, podemos criar um objeto do lado cliente e passar este objeto para o lado servidor.
Do lado servidor o mesmo deverá ser recriado para um objeto compreendido pelo C#.

Para este exemplo, iremos criar um arquivo de javascript com o nome de  DirectEvent.js com o seguinte código:

/**
 Arquivo de definição dos objetos em javascript
 */

// definir o namespace de base para a aplicação
var MyApp = {};

/**
 Define o método listener para o clique do botão
 */
MyApp.Button3Click = function() {
    //Aqui iremos criar um objeto do tipo 'MyInfo', esperado pelo lado servidor
    var result = {
        Nome : Ext.getCmp('TextField2').value,
        Email : Ext.getCmp('TextField3').value
    };

    return result;
};

ASPX Code

<script type="text/javascript" src="../Scripts/DirectEvent.js"> </script>
    <ext:TextField ID="TextField2" runat="server" FieldLabel="Informe seu nome">
    </ext:TextField>
    <ext:TextField ID="TextField3" runat="server" FieldLabel="Informe seu email">
    </ext:TextField>
    <ext:Button ID="Button3" runat="server" Icon="Clock" Text="Enviar">
        <DirectEvents>
            <Click OnEvent="Button3Click">
                <ExtraParams>
                    <ext:Parameter Mode="Raw" Value="MyApp.Button3Click()" Name="dados">
                    </ext:Parameter>
                </ExtraParams>
            </Click>
        </DirectEvents>
    </ext:Button>

Code Behind

Definição do objeto que o lado cliente deverá retornar

/// <summary>
/// Objeto retornado pelo cliente
/// </summary>
struct MeusDados
{
    public string Nome { get; set; }
    public string Email { get; set; }
}

Método que irá receber a informação do lado cliente e converter para o objeto esperado

protected void Button3Click(object sender, DirectEventArgs e)
{
    // Aqui iremos converter o objeto que recebemos do lado cliente
   // em um objeto do tipo "MyInfo"
    MyInfo dados = JSON.Deserialize<MyInfo>(e.ExtraParams["dados"]);
    X.Msg.Show(new MessageBoxConfig
    {
        Buttons = MessageBox.Button.OK,
        Title = String.Format("Olá {0}!", dados.Nome),
        Message = String.Format("Seu e-mail é  {0}.", dados.Email)
    });
}

Neste tópico não iremos abordar como criar  o nosso próprio manipulador de eventos. Este será abordado no tópico “Criando componentes no lado Servidor


É isso ai pessoal 🙂
Até o próximo
♦ Marcelo

About Marcelo

Nascido em Juruaia/MG em uma fazenda de criação de búfalos, e residindo na região Sul do Brasil. Trabalha com desenvolvimento de aplicações desde os 17 anos. Atualmente é Arquiteto Organizacional na Unimake Software. Para saber mais ... http://desenvolvedores.net/marcelo []'s
Page 1 of 612345»...Last »