Há algum tempo sugeri o plugin RockScroll, que exibe o código fonte na barra de rolagem, destacando alguns elementos.
Agora, acabei de ver a dica do Scott Hanselman no twitter de um plugin para o Visual Studio 2010 que tem funções parecidas com o RockScroll.
Bem, não adianta eu falar muito a respeito desse plugin, o mais fácil é instalar e ver pessoalmente. Você pode fazer o download do plugin nesse link.
À primeira vista achei um pouco poluído demais, mas ele tem várias funções que podem ser desabilitadas se necessário, e o melhor, tem o código fonte disponível.
quinta-feira, 15 de abril de 2010
quarta-feira, 14 de abril de 2010
Usando reflection para simplificar nossa vida
Em um projeto que estou trabalhando (o mesmo que citei aqui), existe a necessidade de se comunicar com programas COBOL. A comunicação é feita de uma forma um tanto arcaica. Temos que passar um string contendo os diversos campos, cada um com o tamanho exato.
Digamos que precisamos passar as informações de uma nota fiscal, teríamos essa definição:
Essa classe tem alguns problemas:
Com isso temos uma classe de modelo, contendo os dados devidamente tipados e um mapeamento que define quais campos serão transportados para a string, o formato e a posição. Todas essas informações conseguimos obter usando reflection e o mapeamento definido.
Como você pode ver, não é necessário definir o tipo do campo pois ele é inferido à partir do tipo utilizado no modelo. Ou seja, sabemos que um DateTime deverá ser convertido para o formato yyyy-MM-dd, um inteiro deve ser preenchidos com zeros a esquerda e uma string com espaços a direita.
Para obter a string à partir de um modelo, o que precisa ser feito é chamar um método assim:
conversor.GetString(meuModelo);
O ponto mais importante é a conversão do valor tipado para a string. Como cada tipo tem um formato específico, criei um conversor para cada um. Por exemplo:
Digamos que precisamos passar as informações de uma nota fiscal, teríamos essa definição:
- Data de expedição – 10 caracteres;
- Nome do Cliente – 15 caracteres;
- Número da nota – 5 dígitos;
- Valor – 5 dígitos.
- Data: 01/01/2010;
- Nome: ALGUM NOME;
- Número: 123;
- Valor: 15.
A SOLUÇÃO ANTIGA
A solução que existia para facilitar isso era gerar uma classe de acordo com a definição dos campos, que retorna a string formatada. A classe gerada ficaria assim:class NotaFiscal { private string dataExpedicao; private string nomeCliente; private string numero; private string valor; public string DataExpedicao { get{return dataExpedicao;} set{dataExpedicao = Utils.FormataCaracteres(value, 10);} } public string NomeCliente { get{return nomeCliente;} set{nomeCliente = Utils.FormataCaracteres(value, 15);} } public string Numero { get{return numero;} set{numero = Utils.FormataDigitos(value, 5);} } public string Valor { get{return valor;} set{valor = Utils.FormataDigitos(value, 5);} } public string ObtemString() { return dataExpedicao + nomeCliente + numero + valor; } }
- todos os dados são strings, não é usada a tipagem da linguagem (int, string, DateTime);
- a definição de tipo e tamanho dos campos fica espalhada pelo código;
- a ordem dos campos precisa ser verificada dentro da função ObtemString;
- caso alguma modificação seja feita nos campos, fica ruim de dar manutenção nesse código.
A Nova solução
A solução que procurei desenvolver tinha alguns pré-requisitos, entre eles:- a classe não deveria ter informações relacionadas aos campos que seriam montados na string (definição de tipo, tamanho, etc);
- poder utilizar os tipos da linguagem (int, DateTime, Enum);
- a definição de mapeamento da classe para a string a ser gerada deveria ser clara e facilmente editável;
Com isso temos uma classe de modelo, contendo os dados devidamente tipados e um mapeamento que define quais campos serão transportados para a string, o formato e a posição. Todas essas informações conseguimos obter usando reflection e o mapeamento definido.
class NotaFiscal { public DateTime DataExpedicao { get; set; } public string NomeCliente { get; set; } public int Numero { get; set; } public int Valor { get; set; } }
<mapping class=”NotaFiscal”> <field name=”DataExpedicao” Length=”10” /> <field name=”NomeCliente” Length=”15” /> <field name=”Numero” Length=”5” /> <field name=”Valor” Length=”5” /> </mapping>
Como você pode ver, não é necessário definir o tipo do campo pois ele é inferido à partir do tipo utilizado no modelo. Ou seja, sabemos que um DateTime deverá ser convertido para o formato yyyy-MM-dd, um inteiro deve ser preenchidos com zeros a esquerda e uma string com espaços a direita.
Para obter a string à partir de um modelo, o que precisa ser feito é chamar um método assim:
conversor.GetString(meuModelo);
Implementando a solução
A implementação não tem muito segredo é apenas uma questão de obter cada valor do nosso modelo seguindo a definição do XML.O ponto mais importante é a conversão do valor tipado para a string. Como cada tipo tem um formato específico, criei um conversor para cada um. Por exemplo:
class IntConverter : ITypeConverter { public string GetString(Field field, object value) { int valorInteiro = Convert.ToInt32(value); return valorInteiro.ToString().PadLeft(‘0’, field.Length); } }
Conclusão
Muitas vezes vale a pena perder um pouco de tempo desenvolvendo uma infra-estrutura que facilite o desenvolvimento. Alguns minutos perdidos no início do projeto podem salvar muitos minutos mais adiante, e, o mais importante, evitar muita dor de cabeça.
Marcadores:
Desenvolvimento
segunda-feira, 12 de abril de 2010
Validando bindings usando Reflection
Em um projeto que estou trabalhando percebi a facilidade de criar problemas de binding. Basicamente, ao renomear uma simples propriedade do modelo os bindings ligados a ela param de funcionar. O pior disso é que, para saber que há um problema neles, só mesmo executando as telas uma a uma. Nenhum erro de compilação é gerado.
Pra resolver esse problema, resolvi criar um teste que seja executado automaticamente no build e verifique qualquer problema nos bindings.
Porém, há um pequeno problema, no componente não há nenhuma informação dizendo qual o modelo ao qual ele está relacionado. Sem essa informação, não tem como fazer a validação.
Nesse ponto procurei criar uma forma de descobrir qual modelo está relacionado ao componente.
Olhando a view pude ver um padrão na atribuição de valores utilizados no binding:
Fica claro que, ao fazer um binding temos:
O segundo passo é um pouco mais complicado. Precisamos analisar o código executado, buscando o componente ao qual é atribuído o valor. Via reflection não temos acesso fácil ao código que está sendo executado, para isso foi necessário uma biblioteca externa. Basicamente, foi necessário analisar o MSIL à procura do componente.
Tendo a validação pronta, bastou criar um teste que busca os problemas de binding e reporta cada erro encontrado.
Caso você ainda não conheça o Git, recomendo a leitura desse artigo.
Pra resolver esse problema, resolvi criar um teste que seja executado automaticamente no build e verifique qualquer problema nos bindings.
Relacionando o componente ao modelo
Fazer a validação do binding a princípio é bastante simples, basta buscar cada propriedade do componente que está ligada ao modelo e verificar se o modelo contém a propriedade com o nome definido no componente. Por exemplo, em um ComboBox, pegamos o nome definido no DisplayMember e buscamos no modelo se existe uma propriedade com o nome correspondente.Porém, há um pequeno problema, no componente não há nenhuma informação dizendo qual o modelo ao qual ele está relacionado. Sem essa informação, não tem como fazer a validação.
Nesse ponto procurei criar uma forma de descobrir qual modelo está relacionado ao componente.
A convenção
O projeto está sendo desenvolvido em WinForms e adotamos o padrão MVP, mais especificamente o Passive View. Como você já deve saber, no MVP temos a camada de View (exibição) separada do Presenter (lógica da tela). A view apenas recebe os dados a serem exibidos.Olhando a view pude ver um padrão na atribuição de valores utilizados no binding:
class View { public IEnumerable<Usuario> Usuarios { set{ algumGrid.DataSource = value; } } }
Fica claro que, ao fazer um binding temos:
- uma propriedade do tipo IEnumerable<T>;
- a propriedade permite escrita (contém um set);
- dentro da propriedade atribuímos o valor ao DataSource do componente.
Buscando o modelo correspondente ao componente
Com essa convenção, é possível descobrir quais componentes fazem binding e qual o modelo correspondente. Para isso temos de fazer duas coisas:- buscar todas as propriedades das telas que sejam do tipo IEnumerable<T> e que contenham um set;
- para cada propriedade, obter o componente para o qual está sendo atribuído o valor.
private IEnumerable<PropertyInfo> GetProperties(Type type) { return type.GetProperties() .Where(x => x.CanWrite && EhLista(x)) } private bool EhLista(PropertyInfo propertyInfo) { return TipoEhEnumerable(propertyInfo.PropertyType) || propertyInfo.PropertyType.GetInterfaces().Any(x => TipoEhEnumerable(x)); } private bool TipoEhEnumerable(Type type) { return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>); }
O segundo passo é um pouco mais complicado. Precisamos analisar o código executado, buscando o componente ao qual é atribuído o valor. Via reflection não temos acesso fácil ao código que está sendo executado, para isso foi necessário uma biblioteca externa. Basicamente, foi necessário analisar o MSIL à procura do componente.
Finalmente, Validando os bindings
Agora que já temos todas as informações necessárias, fazer a validação dos bindings é simples. Basta pegar cada propriedade do componente que faz o binding e verificar se o campo existe no modelo. Essa verificação varia conforme o componente, por exemplo, em um ComboBox simplesmente verificamos o DisplayMember e o ValueMember. Em um grid, por sua vez, é necessário verificar essas propriedades para cada uma das colunas.Tendo a validação pronta, bastou criar um teste que busca os problemas de binding e reporta cada erro encontrado.
Código fonte de exemplo
Pra entender melhor isso, o mais fácil é olhar o código fonte. Disponibilizei o código no GitHub, nesse repositório.Caso você ainda não conheça o Git, recomendo a leitura desse artigo.
Marcadores:
Desenvolvimento,
UI
Assinar:
Postagens (Atom)