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.