Gerência do ciclo de vida de uma aplicação

Já se passaram um ano e meio aproximadamente desde que Eduardo Klein falou sobre automação de build e deploy no M3 aqui no blog. Em seu post, ele focou em aspectos mais conceituais desse processo. De lá pra cá, o produto M3 evoluiu bastante e diversos outros projetos foram iniciados na Mobiltec. Por isso, entendemos que agora é um ótimo momento para revisitar o assunto. O objetivo desse post, portanto, é abordar novamente o tema build e deploy, mas dessa vez com um foco maior nas ferramentas utilizadas durante todo o processo de desenvolvimento.

Antes de seguir adiante é importante deixar claro sobre o que exatamente estamos falando:

O termo comumente encontrado é ‘ALM’, que significa “Application Lifecycle Management”, ou “Gerenciamento do ciclo de vida de aplicações”. Apesar do nome bastante descritivo, o conceito de “ciclo de vida” para uma aplicação envolve diversas etapas. Algumas delas já foram citadas pelo Eduardo, como o controle de versão de código, o build e o processo de deploy (também chamado de “Release Management”, ou apenas “Release”). Além dessas, também foi citada a etapa de testes, da qual falaremos mais adiante de forma diferenciada para testes unitários, testes de integração e testes de sistema.

A imagem a seguir representa o conjunto de ferramentas de ALM da Microsoft, na época do Visual Studio 2010. De todos esses componentes, nem todos são utilizados pela Mobiltec (como as ferramentas integradas do Office com o TFS), e outros ainda nem existiam, como o Release Management que citaremos em seguida.

Motivação

Deixando de lado as terminologias por um momento, qual a grande motivação de seguir essas etapas? Por que mantemos diversos servidores, cada um com vários serviços especializados para cumprir cada passo? Como foi muito bem colocado no artigo antigo, o custo (tanto em equipamentos quanto em conhecimento das ferramentas) se justifica rapidamente, e nos ajuda a ser cada vez mais produtivos.

Recentemente, uma forte discussão surgiu em relação à prática de Test Driven Design (TDD) na comunidade de desenvolvedores, por conta de um post polêmico escrito por David Heinemeier. Todo o desdobramento que se seguiu pode ser acompanhado nesse resumo, feito pelo Martin Fowler, que também participou da discussão. Mesmo aos que não gostam da área de testes recomendo fortemente que assistam.

O que essa série de discussões sobre testes tem a ver com o nosso assunto? Diretamente, nada. No entanto, um dos pontos mais importantes, enfatizados em todos os vídeos em diversos momentos, é a necessidade de feedback sobre o desenvolvimento. Quando escrevemos testes (e os executamos com frequência), temos a garantia de que os comportamentos esperados estão corretos, e consequentemente podemos melhorar o design das classes afetadas e mantê-las com maior segurança.

Outro processo que tem muita relação com esse conceito é o desenvolvimento ágil, mais especificamente o Scrum, que nos últimos meses vem sendo adotado por diversos de nossos times de desenvolvimento. Novamente, o que existe em comum entre o Scrum e as ferramentas a serem discutidas (além de outros fatores que não entraremos no mérito aqui) é o feedback que obtemos do processo como um todo. Como existem iterações de tempo fixo, ao final de cada etapa podemos analisar a resposta do time ao Sprint anterior, ver os problemas e atacá-los com objetividade, para que as iterações seguintes sejam cada vez mais eficientes.

Se pensarmos pelo lado de “validação”, esses dois mecanismos se assemelham bastante às etapas do desenvolvimento. O servidor de build, por exemplo, não passa de mais um mecanismo de validação, que nos dá mais informação para corrigir problemas que seriam detectados muito mais tarde se o processo não existisse. A ferramenta de release também serve esse propósito, pois automatiza um algoritmo potencialmente complexo de instalação, sujeito a falhas humanas, de forma que a atualização de versão em um ambiente de produção se reduza a uma “decisão”, e não mais a um “problema”: o pipeline de release é disparado com um único clique e a aplicação é instalada automaticamente no destino.

Ferramentas

Abaixo, listamos as principais ferramentas utilizadas. Nosso objetivo aqui é dar uma visão geral sobre as mesmas, explicando onde elas se inserem em nossos processos. E novos posts vamos aprofundar um pouco mais cada uma delas.

- Team Foundation Server (TFS):
Sistema de controle de versão de código da Microsoft. É o ponto de entrada do nosso pipeline de desenvolvimento. Permite a aplicação de diversos processos de desenvolvimento como o Agile ou o Scrum, através de atividades, chamadas de “work items”, que mantêm o histórico de modificações e requisitos de um projeto. Idealmente instalado em uma máquina física ou virtual dedicada.

- TFS Build:
Subcomponente do TFS, responsável por fazer o processamento dos arquivos fonte em um ambiente isolado, onde é possível reproduzir compilações e testes unitários de forma consistente, sem particularidades de uma máquina de desenvolvedor. Dividido em dois outros módulos principais:

  1. Build Controller: gerencia de forma centralizada as requisições de build dos diversos projetos. Sua responsabilidade é coordenar os agentes de build para executar as requisições provenientes de todos os projetos atrelados à instância do TFS. O controlador é um componente relativamente leve e, portanto, pode conviver sem problemas no mesmo computador que o TFS principal.
  2. Build Agents: serviços que executam o workflow de build propriamente dito, de acordo com o que for indicado pelo controller. Eles devem ser configurados em máquinas com características específicas para cada projeto, e requerem uma boa configuração de hardware para manter o processo de validação o mais responsivo possível.

- Microsoft Test Manager:
É um componente relativamente novo, ótimo para gerência de testes de sistema, de carga e de interface. É integrado ao TFS pelos chamados planos de teste (TestPlans). Cada plano contém inúmeros Test Cases (um tipo específico de work item), que por sua vez são linkados a User Stories ou Product Backlog Items, que representam os requisitos do software. Permite a configuração de topologias de teste avançadas, utilizando diretamente máquinas virtuais gerenciadas pelo Hyper-V ou pelo SCVMM (System Center Virtual Machine Manager). Pode ser ativado tanto pelo processo de build, via workflow de Lab Management ou na etapa de release via commandline. De forma análoga ao build, também é dividido em dois módulos:

  1. Test Controller: centraliza as requisições de processamento de planos de teste, delegando aos agentes definidos no plano a execução dos testes associados.
  2. Test Agent: serviço que executa os testes passados pelo controller com as configurações especificadas.

- Release Management:
Ferramenta recente de gerenciamento de releases. Antes conhecida como InRelease e desenvolvida pela InCycle Software, foi adquirida pela Microsoft no início do ano passado e oficialmente lançada em Novembro de 2013. Permite a criação e configuração de um pipeline customizado de release, com etapas definidas pelo usuário, mecanismos de transição entre etapas, configuração por ambiente, etc. Featureset bem similar ao Octopus Deploy, também utilizado internamente por alguns projetos da Mobiltec. Possui três componentes distintos:

  1. Release Management Client: aplicação que permite a configuração do pipeline de release. Em geral instalado em todas as máquinas de desenvolvedores que participam do projeto.
  2. Deployment Server: análogo ao Build e Test controllers, se comunica com o mecanismo de build do TFS para iniciar os processos de release.
  3. Deployment Agent: instalado nas máquinas destino para permitir o download e execução da instalação dos artefatos do build remotamente.

Por hora é isso! Em um próximo post, mostraremos detalhes da primeira ferramenta, o TFS como source control. Discutiremos algumas vantagens e desvantagens de estratégias de branch distintas, como estruturar os projetos e solutions, tipos de projeto e work items, checkin policies, entre outros.

Fique atento!

Escrito por Juliano Gonçalves

Windows Workflow Foundation na prática

O Windows Workflow Foundation, integrado ao .NET Framework 3.0 em 2006, é uma tecnologia desenvolvida pela Microsoft que fornece um conjunto de bibliotecas para auxiliar no desenvolvimento de fluxos de trabalho de maneira rápida e fácil. Oferece uma linguagem de mais alto nível para criação de tarefas paralelas e assíncronas, além de fornecer suporte a escalabilidade das aplicações. Em um post anterior do blog, foi apresentada uma visão geral da tecnologia, que você pode conferir aqui.

Agora, o objetivo é apresentar o Windows Workflow na prática! Vamos criar uma aplicação bem simples, utilizando alguns elementos da tecnologia. A aplicação deve calcular os n primeiros dígitos da sequência de Fibonacci. O fluxo está descrito abaixo.

1. Solicitar ao usuário o valor de n. Em caso de entrada inválida, exibir uma mensagem de erro e encerrar.
2. Calcular a sequência de Fibonacci.
3. Exibir a sequência na tela.

Primeiro, vamos criar um novo projeto. Na janela de novo projeto, em Templates -> Visual C# -> Workflow, escolha a opção Workflow Console Application, como na Figura 1.


Figura 1

Delete o arquivo Workflow1.xaml e adicione um novo fluxo com um nome mais apropriado, como FibonacciWorkflow.xaml, conforme a Figura 2.


Figura 2

Na classe Program.cs, onde o fluxo é criado e chamado pelo Main da aplicação, faça as alterações necessárias para que o novo fluxo seja usado, conforme código abaixo. A última linha foi adicionada para que o console seja fechado após alguma tecla ser digitada, de modo que o usuário possa visualizar a resposta.

static void Main(string[] args){
       Activity workflow = new FibonacciWorkflow();
       WorkflowInvoker.Invoke(workflow);

       Console.ReadKey();
}

 

O primeiro passo do fluxo é solicitar o valor de n para o usuário. Por isso, adicionamos uma atividade do tipo WriteLine no fluxo. Essa atividade deve ser adicionada dentro de uma sequência, visto que teremos que adicionar mais atividades depois. A Figura 3 ilustra esse passo.


Figura 3

Em seguida, devemos verificar se a entrada é válida. Para isso, tentaremos converter essa entrada em inteiro e, em caso positivo, passá-lo com parâmetro para uma atividade calcular a sequência. Em caso negativo, uma mensagem de erro deve ser exibida. Adicionaremos uma variável ao fluxo para armazenar o inteiro, conforme Figura 4.


Figura 4

Para converter a entrada, vamos utilizar o método Parse para Int32, armazenando na variável recém criada. Como esse método pode disparar uma exceção em caso de falha na conversão, iremos colocar o restante da lógica em uma instrução Try-Catch. Em caso de exceção, uma mensagem de erro deve ser exibida. A Figura 5 ilustra esses passos.


Figura 5

Se a entrada for um inteiro, devemos passá-lo a uma atividade que irá calcular a sequência de Fibonacci. Vamos criar essa atividade antes de prosseguir com o fluxo. Para isso, devemos inserir uma CodeActivity, conforme Figura 6.


Figura 6

A atividade precisa de um argumento de entrada inteiro, o valor de n, e um argumento de saída, a lista de números da sequência. Isso é feito adicionando os seguintes membros a atividade:

public InArgument N { get; set; }
public OutArgument> Sequence { get; set; }

O método Execute é responsável por executar a atividade, por isso deve conter a lógica para calcular a sequência de Fibonacci, além de popular o argumento de saída. Nesse exemplo, o corpo do método é bem simples, conforme código abaixo.

protected override void Execute(CodeActivityContext context) {
         int n = context.GetValue(this.N);

         List sequence = CalculateSequence(n);

         Sequence.Set(context, sequence);
}

 

Recompilando a solução, a atividade recém criada aparece na Toolbox e pode ser utilizada no nosso fluxo agora, conforme a Figura 7.


Figura 7

Nas propriedades da atividade, devemos especificar qual variável será passada com parâmetro n e qual variável receberá o valor de retorno da atividade. A Figura 8 ilustra esse passo, considerando que uma variável local já foi criada para armazenar o retorno.


Figura 8

Por último, devemos exibir a sequência na tela, inserindo um laço ForEach logo após a atividade do cálculo, conforme Figura 9.


Figura 9

O laço deve apenas percorrer a sequência e escrever na tela, conforme a Figura 10.


Figura 10

O objetivo desse exemplo simples é introduzir o Windows Workflow Foundation na prática, por isso ainda não fizemos uso de todos os recursos que oferecem suporte ao paralelismo e a escabilidade. Então, o que achou? Qualquer dúvida ou sugestão, deixe nos comentários.

Escrito por Paula Burguêz

Estimativa de Esforço e o Pensamento Ágil

A adoção de uma metodologia de desenvolvimento ágil de sistemas traz mudanças significativas ao trabalho do time, sejam estas por práticas inerentes à metodologia adotada, mas também mudanças de paradigma relacionadas ao pensamento ágil.

A estimativa de esforço de desenvolvimento é uma prática onde ocorre mudanças impactantes. No Scrum, por exemplo, esta atividade é realizada pelo próprio time de desenvolvimento, principalmente nas etapas de refinamento do Product Backlog e planejamento do Sprint Backlog.

Recentemente iniciamos a adoção do Scrum em novos times de desenvolvimento que foram formados na Mobiltec, onde muitos dos profissionais envolvidos não possuem experiência prática com a metodologia em questão. Os primeiros Sprints apresentaram problemas naturais de estimativa, originados tanto da falta de histórico de referência como também da carência de um processo adequado para realizar as estimativas.

Nestes primeiros Sprints o Burndown fugiu bastante da expectativa, o que é aceitável visto que o time não tem histórico para identificação de sua velocidade real. No entanto, alguns sintomas foram identificados durante o processo. A análise e correção desses pontos auxiliaram o time a evoluir sua prática de estimativa:

  1. Embora o time se sentia satisfeito com os resultados do Sprint Planning, esta cerimônia estava tomando menos da metade do tempo que a metodologia aponta como adequado.
  2. As estimativas, que a uma primeira vista pareciam coerentes, eram consolidadas apenas por parte do time. Os demais estimavam influenciados pelos mais experientes ou apenas se omitiam.
  3. Mesmo com o atraso evidente do Sprint, visualizado no Burndown, percebeu-se que em alguns momentos um ou mais desenvolvedores do time estavam abaixo de sua produtividade, principalmente quando os mais experientes (aqueles que realizavam a estimativa de esforço) não estavam presentes.

Com a observação destes fatos, conseguimos identificar algumas falhas do nosso processo de planejamento. Como o processo de estimativa não estimulava a participação de todos, muitos problemas de entendimento de escopo e de meios de implementação não eram explicitados. Isto gerou uma dependência, que baixou a produtividade geral do time em momentos de ausência dos mais experientes, pois explicações sobre os objetivos se faziam necessárias a qualquer momento.

Com isso, houve o entendimento de que era necessário evoluir as práticas de planejamento. Todos os membros do time de desenvolvimento precisam sair da reunião de Sprint Planning com o claro entendimento dos objetivos do Sprint. O time debateu e optou pelo uso do Planning Poker, técnica de estimativa mais difundida entre praticantes do Scrum, pois entendeu que esta prática apóia a redução dos problemas identificados.

Para quem não conhece, o Planning Poker é uma técnica para realização de estimativas que utiliza como ferramenta um baralho individual, que pode ser físico ou virtual.  Esse baralho segue uma numeração próxima a sequencia de Fibonaccci para que as diferenças entre as complexidades não sejam muito baixas. Na reunião de planejamento do backlog, cada uma das user stories é apresentada ao time pelo Product Owner, para que os membros questionem e esclareçam suas dúvidas. Após, o time avalia a complexidade dessa tarefa. Cada um dos membros deve selecionar a carta com a complexidade estimada em story points, colocando-a na mesa com a face do valor oculta. Quando todos já tiverem feito suas escolhas, as cartas devem ser viradas e exibidas ao mesmo tempo. Assim, nenhum integrante é influenciado pela escolha de outro membro.

Os benefícios do Planning Poker

Durante os primeiros Sprints, o time trabalhou as estimativas sem processo formal. Após a sua adoção, foi possível observar diversos avanços:

  • Evita a influência dos participantes: Esta é justamente a proposta do Planning Poker, razão pela qual a técnica é muito difundida. Como durante o Poker todos devem apresentar suas estimativas ao mesmo tempo, cada um é forçado a pensar de forma independente.
  • Valida o entendimento do escopo: O fato de cada participante ter que pensar na sua estimativa individualmente lhe força o correto entendimento das funcionalidades. Antes do Planning Poker, era possível seguir o planejamento sem o correto entendimento, apenas aceitando as estimativas de outros participantes. Com o Planning Poker, discrepâncias de estimativas são um ótimo alerta de que alguém não está com o correto entendimento.
  • Aceita a incerteza: Ao utilizar a sequencia de Fibonacci o Planning Poker estimula os participantes a trabalhar com a ideia de estimar itens maiores com relativo grau de incerteza, deixando o processo mais leve e ágil.

Os desafios da mudança de paradigma: Story Points x Tempo

É muito comum em times iniciando no Scrum passar pela dificuldade de estimar o tamanho das estórias (Story Points), pois esta é uma unidade relativa de medida, que envolve esforço, complexidade e risco, entre outros fatores.

Como é uma unidade relativa, é preciso de uma base de comparação para criação das relações. Justamente porque os times estão iniciando o processo, precisam formar esta base para nortear o processo de estimativa de Story Points.

Em postagem publicada na comunidade da Scrum Alliance, Narasimhan Anantharangachar sugere uma abordagem evolutiva bem interessante para estimativas de estórias:

1. Quebra de estórias em tarefas e estimativa de tarefas em horas

Esse é o ponto de partida típico para times que são novos no Scrum. É relativamente fácil de fazer e já inicia-se a construção de um histórico para medição futura de velocidade.

Pontos negativos de estimativa de esforço em horas:

  • É comum a quebra de tarefas no ciclo análise, projeto, desenvolvimento, teste, que gera muita dependência entre tarefas e membros do time e, consequentemente, muito desperdício de tempo em espera.
  • Inconsciente ou não, estimativa acaba virando meta. Os membros do time tendem a tentar cumprir o tempo estipulado, mesmo este estando errado (para cima ou para baixo). Tarefas subestimadas tendem a ficar com qualidade comprometida, enquanto que para o caso das tarefas superestimadas a teoria da “expansão dos gases” acaba prevalecendo.

2. Quebra de estórias em tarefas, sem estimativa de esforço de tarefas em horas

Aqui o time ainda quebra as estórias em tarefas para ter uma base para a estimativa da estória, mas não mais atribui um tempo específico por tarefa. O autor aponta benefícios importantes nesta abordagem:

  • Como as tarefas não possuem uma estimativa de esforço, os membros do time podem aplicar o tempo necessário para implementá-las com qualidade, sem prejudicar o objetivo do Sprint.
  • Promove a colaboração do time pois não há um controle apurado sobre início e término das tarefas. No longo prazo a colaboração em times Ágeis é um benefício.
  • O progresso é medido por estórias completadas, que agregam valor ao usuário, e não por tarefas completadas, que tem menor significado.

Pontos negativos:

  • A divisão em tarefas ainda gera dependência desnecessária entre membros do time, que gera desperdícios de tempo.

3. O time estima apenas as estórias, sem desmembrá-las em tarefas

Aqui o time se compromete não mais com tarefas, mas com estórias que agregam valor para o usuário do sistema. Segundo o autor é neste estágio que o time apresenta os melhores resultados pois:

  • A colaboração aumenta, pois os membros do time precisam interagir mais para desenvolver juntos as estórias, inclusive praticando pareamento.
  • Os membros do time se focam em desenvolver tarefas necessárias para cumprir com a estória, ao invés de simplesmente concluir itens de uma lista.
  • Do ponto de vista de resultados, é positivo pois o Burndown apresenta uma visão mais real do que está realmente pronto

Referências

Escrito por Eduardo Klein

UML Revisitada

Para que serve UML mesmo?

Todo (ou quase) profissional de TI se deparou em algum ponto da sua carreira com UML (Unified Modeling Language), mas apesar disso, é comum desenvolvermos software sem a utilização da mesma.
Então, se desenvolvemos sem ela e nossos softwares continuam funcionando, por que a UML é um assunto recorrente no meio acadêmico e profissional? A resposta é que a UML se insere no contexto da documentação, análise e visualização de um sistema, desempenhando este papel em diversos níveis.

Por isso, vamos revisitar o assunto UML, dando uma olhada no diagrama de Casos de Uso e no diagrama de Sequência. Outros tipos de diagramas, além de considerações interessantes sobre UML e Agile podem ser vistos nesse post, escrito por Eduardo Klein.

Diagramas

A UML possui 14 diagramas, divididos em duas categorias: diagramas Estruturais e diagramas Comportamentais.
Abaixo veremos dois diagramas comportamentais (Casos de Uso e Sequência).

Diagrama de Casos de Uso

Um caso de uso tem por objetivo definir um requisito ou funcionalidade de um sistema e ilustrar o sistema com alto nível de abstração.

Nesse diagrama, representamos os casos de uso e as associações entre eles, atores e outros casos de maneira macro, sem quaisquer detalhes técnicos, que devem ser apresentados na documentação do caso de uso.

O diagrama de casos de uso é composto por:

  • Ator
  • Caso de Uso
  • Associação

Ator: Usuário ou sujeito que executa ação, interage com o sistema.
Caso de uso: Funcionalidade, requisito.
Associação: Relação entre casos de uso ou entre caso de uso e ator.

Exemplo:

Diagrama de casos de uso
Diagrama de casos de uso de um restaurante

Como podemos ver, o diagrama apresenta as funcionalidades do sistema em um nível em que qualquer um pode entender, inclusive o usuário final.

Exemplo mais complexo:

Diagrama de casos de uso
Diagrama de casos de uso de uma locadora

Neste exemplo, vemos três coisas diferentes do primeiro: generalização/especialização, include e extend.

Generalização/Especialização: Indica herança. No caso acima, o perfil administrador pode executar as mesmas funcionalidades que Cliente e Funcionário, mas Cadastrar Fornecedor como funcionalidade exclusiva.

Include: Indica que a execução de determinada funcionalidade implica na execução de outra. No caso acima, Locar Filme e Devolver Filme implicam na execução de Imprimir Cupom Fiscal.

Extend: Indica que a execução de um método implica na execução ou não de outro. No caso acima, ao efetuar Locar Filme, um Funcionário pode Sugerir um Filme, caso o Cliente seja um cliente antigo. Restrições ou notas podem ser ilustradas no diagrama.

Diagrama de Sequência

Representam a interação entre objetos do sistema, tem por objetivo examinar partes pequenas e complexas do sistema e dão ênfase na ordem temporal das mensagens.

O diagrama de sequência é composto por:

  • Objetos/Atores
  • Mensagem
  • Linha de vida

Objetos/Atores: objetos/Atores interagindo.
Mensagem: comunicação entre objetos.
Linha de vida: identifica no topo o objeto representado, seguido abaixo por uma linha vertical tracejada, indicando as interações do objeto em relação ao tempo.

Exemplo:

Como pode ser visto no exemplo acima, temos a identificação dos objetos no topo e abaixo, da esquerda para a direita, as suas interações.

Setas preenchidas: representam interações que requerem respostas.
Setas tracejadas: representam a resposta de uma interação.
Setas vazadas: representam interações que não requerem resposta.

É perceptível que o diagrama de sequência pode ser complementar ao diagrama de casos de uso, podendo detalhar em um nível mais micro uma determinada funcionalidade.

Considerações

Ainda que tenhamos visto diagramas de níveis de detalhamento técnico diferentes, ambos servem para o propósito de ilustrar e documentar o problema.

Tanto o diagrama de casos de uso quanto o diagrama de sequência podem ser apresentados e entendidos por pessoas que não fazem parte do processo do desenvolvimento, bem como auxiliar o desenvolvedor na codificação. Enfim, fazendo coro ao já mencionado por Eduardo Klein, em tempos de metodologias ágeis, ainda existe espaço e valor na UML.

Escrito por Luciano Luzzi

Você já usa Asp.Net Dynamic Data? Deveria!

Asp.Net  Dynamic Data é uma tecnologia criada no .Net 3.5 e utilizada no contexto Web Forms para gerar elementos de interface de forma dinâmica, centralizando a lógica de visualização e roteamento, consequentemente diminuindo redundância de código e evitando problemas e inconsistências na apresentação dos dados em uma aplicação web.

A ideia por trás da ferramenta é bastante simples: ao invés de cada página mostrar os dados da sua forma, com seus controles web/componentes html, cada classe tem o seu “exibidor” padrão, que pode ser customizado de acordo com a necessidade de cada componente.

Esse tipo de funcionalidade não é exclusivo do Asp.Net Web Forms: aplicações baseadas em xaml (como WPF, Silverlight e Windows Phone) podem  utilizar nativamente o conceito de DataTemplates, que têm função similar. O MVC Scaffolding fornece views/controllers autogerados por templates T4 obtendo um resultado semelhante. O próprio Ruby on Rails, que foi inspiração para criação do Dynamic Data pela Microsoft, ou o Django, escrito em Python, utilizam um mecanismo análogo.

Como utilizar

Existem basicamente duas formas de utilizar Dynamic Data:

  • ‘Full Scaffolding’:
    Web site completamente autogerado pelo mecanismo do Dynamic Data através de rotas dinâmicas e templates baseados diretamente no contexto Entity Framework do banco. Páginas inteiras são criadas automaticamente para todas as classes, incluindo filtros de consulta e CRUD básico. Esse é um ótimo método para se começar uma aplicação ‘Data Driven’ do zero, sendo fortemente recomendado o template padrão de aplicação Web Forms Dynamic Data do próprio Visual Studio como um bom ponto de partida.

  • Incremental:
    Pequenas funcionalidades do Dynamic Data são utilizadas sob demanda (em páginas específicas de uma aplicação existente).

A Estação de Gerência do M3, nosso caso de uso em produção, é uma aplicação Web Forms bastante extensa e vem sendo mantida há vários anos. Por isso o time resolveu adotar a forma incremental e utilizar apenas um conjunto pequeno, mas poderoso, de funcionalidades em algumas páginas novas.

As features que iremos discutir aqui são as que de alguma forma foram aplicadas na EG. Para contextualizar o restante do post, utilizaremos algumas classes que criamos em um projeto de demonstração contendo todas as etapas que iremos discutir. Você pode fazer o download da versão final desse projeto clicando aqui.

A base para o início do entendimento do sistema de templates são os templates em si. Quando um projeto Dynamic Data é criado, ou quando o pacote nuget do Dynamic Data é instalado em uma aplicação, uma pasta chamada DynamicData é criada na raiz do site.

DynamicDataFolderStructure

Note que existem 3 pastas principais com os templates de exibição, contemplando User Controls (.ascx) e páginas (.aspx).

  • FieldTemplates se referem aos templates de exibição de campos das classes;
  • EntityTemplates são templates para exibição de classes inteiras;
  • PageTemplates são as páginas dinâmicas que exibem qualquer tipo de conteúdo baseado nas rotas geradas

Desses, nos concentraremos em FieldTemplates, amplamente utilizados nas novas páginas do M3.

FieldTemplates:

Dentro da pasta FieldTemplates, cada template pode ter variações, sufixadas por _Edit e _Insert. O mecanismo do Dynamic Data é responsável por carregar o template adequado de acordo com o modo de exibição atual do controle. Tomemos como exemplo um controle databound como o DetailsView ou FormView: quando ele está em modo de inserção, os templates com sufixo _Insert são carregados; Quando o modo selecionado é o de edição, o _Edit é escolhido; Já quando o modo for de visualização, o controle sem sufixo é utilizado.

Para a carga desses FieldTemplates, existe um processo de fallback automático com prioridades, que identifica o UserControl  a ser utilizado. Como exemplo, tomamos o fluxo para o modo de inserção:

  1. Se um template com sufixo _Insert existe, ele é utilizado;
  2. Senão, se um template com sufixo _Edit existe, ele é utilizado;
  3. Senão, se um template sem sufixo existe, ele é utilizado;
  4. Senão, o comportamento padrão sem templates dinâmicos é utilizado (equivalente ao BoundField)

A decisão sobre qual nome de template é procurado na pasta é feita por convenção. Por exemplo, o Text.ascx é o template padrão de campos string, enquanto o Boolean.ascx é o padrão para campos do tipo bool. É possível ainda que campos com o mesmo tipo utilizem templates distintos, e é nesse momento que entram em cena os atributos do namespace System.ComponentModel.DataAnnotations, essenciais para customizar o processo de geração da interface.

Uma propriedade string marcada com o atributo [DataType(DataType.EmailAddress)] utiliza o template EmailAddress.ascx, enquanto uma outra propriedade, também string, marcada com [DataType(DataType.Url)] utiliza os templates Url.ascx e Url_Edit.ascx. Se um campo é marcado com o atributo UIHint, o template selecionado pode ser especificado de forma explícita. Por exemplo, ao ler a propriedade a seguir, o DynamicData procura por um template com o nome ‘Custom’, seguindo o algoritmo descrito acima, ou seja, “Custom.ascx”, “Custom_Edit.ascx”,  etc.

[UIHint("Custom")]
public string Name { get; set; }

 

Pela quantidade de arquivos na pasta, fica evidente que os FieldTemplates fornecidos pelo pacote nuget ou pelo projeto padrão não são exaustivos. Se observarmos a enumeração DataType apenas, veremos que existem diversos tipos de dados listados que não têm templates associados, como CreditCard, PostalCode ou PhoneNumber. O sistema de templates foi criado para ser o mais flexível possível, permitindo a customização da exibição dos dados independente do contexto da aplicação. Dessa forma, basta criar um novo controle e disponibilizá-lo na pasta FieldTemplates com o nome certo para que ele seja carregado corretamente. O Visual Studio fornece inclusive um arquivo padrão para os user controls utilizados como Field Templates:

NewFile-FieldTemplate

Esse é o output gerado pelo VS ao selecionar o “Dynamic Data Field”:

Por padrão, são gerados o template normal (sem sufixo) e o de edição (_Edit), que costumam ser os mais utilizados, já que os controles visuais de edição e inserção tendem a ser os mesmos para as duas operações. Caso exista uma razão para customizar a inserção ou se queira separar bem os códigos da implementação de cada um, basta criar um terceiro arquivo ascx com o sufixo _Insert. Todas as implementações geradas herdam da classe base FieldTemplateUserControl, concebida para nos auxiliar com funções comuns relacionadas à formatação e ao binding dos campos com os elementos visuais.

Na maioria dos casos a customização se dá através do markup (arquivo ascx), mas em alguns momentos é preciso customizar a lógica de processamento dos controles. Nessas situações é sempre interessante se basear nos controles existentes e entender o seu funcionamento para que a lógica fique simples e utilize corretamente as classes fornecidas pelo framework.

Alterando uma página para utilizar Dynamic Data

O núcleo de todo mecanismo de scaffolding do Dynamic Data está na classe MetaModel. Essa classe, juntamente com suas MetaTable’s e MetaColumn’s, representam a informação visual e relacional dos objetos a serem exibidos. Quando o mecanismo é utilizado pela primeira vez, uma MetaModel padrão, contendo os tratadores implementados no próprio framework, é criada e armazenada para uso na aplicação. Através dessa classe é possível até mesmo alterar o responsável por gerar os templates, modificar a lógica de seleção (baseada em sufixos) ou alterar o caminho da pasta de onde os templates são lidos.

No caso dos FieldTemplates, o principal elemento é a presença de uma instância de um MetaColumn. Quando o sistema é informado que deve utilizar Dynamic Data, um MetaTable é gerado para o tipo especificado (no caso do nosso projeto de testes, para a classe Device) e um conjunto de MetaColumns é criado contendo informação de cada campo presente na nossa model. Quando o FieldTemplate correto é instanciado, o objeto MetaColumn correspondente ao campo é repassado, servindo de contexto para as lógicas de interface dentro do controle. Essa informação é posteriormente acessada pela propriedade Column do FieldTemplate e utilizada, de alguma forma, por praticamente todos os templates.

A forma mais simples de “ativar” Dynamic Data em uma página é através dos extension methods em INamingContainer, EnableDynamicData. Todos os controles de databinding do web forms implementam a interface INamingContainer, portanto basta chamar esse método diretamente em um dos controles para habilitar o mecanismo. No nosso projeto, utilizaremos o evento Init dos componentes para ligar o DynamicData:

No aspx da nossa página:


 

E no code behind:

protected void EnableDynamicData(object sender, EventArgs e)
{
    ((INamingContainer)sender).EnableDynamicData(typeof(Device));
}

Após a execução desse método, nossos controles customizados já são chamados. Para verificar isso, basta colocar um breakpoint em um dos controles padrão (como o Text.ascx) ou editar o ascx e observar as mudanças na tela.

Apesar de a extensão ser sobre a interface INamingContainer, o controle de datasource associado deve suportar o mecanismo de Dynamic Data através da implementação da interface IDynamicDataSource. Controles de datasource padrão do aspnet como o EntityDataSource e o LinqDataSource suportam esse mecanismo, enquanto o ObjectDataSource pode ser extendido para funcionar com Dynamic Data facilmente, como pode ser visto nesse link.

Customizando o comportamento

Anteriormente mencionamos o uso de alguns atributos do ComponentModel.DataAnnotations para controlar a seleção do template a ser utilizado. Esse mesmo namespace contém diversos outros atributos bastante úteis para manipulação da interface através do Dynamic Data. Todos os atributos default são convertidos para propriedades do objeto MetaColumn, que é repassado para os controles. Dessa forma é possível acessar os seus valores de maneira tipada sem utilizar reflection. Lembrando aqui que vários desses mesmos atributos são utilizados por frameworks de ORM como o Entity Framework para geração do schema do banco. A lógica é centralizada de maneira elegante em um único ponto, facilitando a manutenção e o entendimento. Vejamos alguns deles e qual a sua utilidade no contexto de interface do usuário:

Required – Torna o campo anotado obrigatório. Os templates padrão utilizam esse valor para habilitar/desabilitar um RequiredFieldValidator e setar sua mensagem de erro.

StringLength – Controla o tamanho máximo/mínimo de uma string. Utilizado nos templates para setar o tamanho máximo dos textboxes.

DefaultValue – Utilizado para fornecer um valor default no modo de inserção nos templates.

Display – Um dos atributos mais importantes para geração da UI. Possibilita modificar o texto de exibição do controle tanto para campos (Name) quanto para headers da Grid (ShortName). Permite também setar a descrição do campo (Description) utilizado em tooltips, a ordem de apresentação dos campos (Order) e se o campo e seus filtros devem ser autogerados ou não.

DisplayFormat – Formatação dos valores. Utilizado em diversos pontos nos templates. Comum em campos de data e hora.

Readonly – Marca a propriedade como ReadOnly. Atrelado à propriedade Readonly dos campos autogerados.

Editable – Indica se o valor pode ser alterado quando em modo de edição (repassado para as propriedades ReadOnly dos controles em modo de edição nos templates) e permite setar um valor na criação.

Range – Validação de intervalo para números. Atrelado a um RangeValidator nos controles numéricos .

RegularExpression – Atrelado a um RegularExpressionValidator nos templates.

ScaffoldColumn – Permite modificar a geração da MetaTable para omitir o MetaColumn referente a essa coluna (diferente da opção de AutoGenerate no Display, que apenas inibe a geração automática mas mantem o campo no MetaColumn)

Filtrando os dados dinamicamente

Até agora falamos bastante sobre a exibição das nossas classes e como controlar isso, mas Dynamic Data não se restringe somente a exibição. Na introdução, comentamos sobre rotas dinâmicas (utilizadas em sites com full scaffolding geralmente) e em filtro de dados.

É muito comum que controles sejam especificamente feitos para filtrar dados em tabelas e eles são quase sempre baseados na tabela em questão, bem como no tipo dos dados sendo filtrados. Dynamic Data tem uma feature bastante poderosa que são os QueryableFilterUserControl’s.

Como esse post é sobre Dynamic Data, até aqui não demos muita importância para o restante dos tópicos, como Databinding em Web Forms, Entity Framework (no caso do nosso projeto demo, Code First Entity Framework) e agora também não entraremos muito no mérito do controle QueryExtender. Mais informações sobre como utilizar esse, que é um dos controles mais poderosos do Web Forms (opinião livre), podem ser encontradas aqui.

Para podermos utilizar os filtros dinâmicos gerados pelo Dynamic Data, precisamos adicionar alguns controles a nossa página. Nosso form até então continha apenas um controle de datasource e um controle de exibição atrelado a ele (no caso, um EntityDataSource e uma GridView/DetailsView). Adicionaremos agora um QueryExtender e um QueryableFilterRepeater da seguinte maneira:


 

O QueryExtender é atrelado ao nosso datasource, permitindo assim extensão da consulta realizada adicionando filtros ao IQueryable antes de sua execução, dependendo dos controles declarados. Por sua vez, o QueryableFilterRepeater fica encarregado de gerar todos os filtros que fazem sentido para a nossa model.

 

Se compilarmos e executarmos nossa página nesse momento, um erro será exibido:

 

 

Dynamic Data foi adicionado ao projeto que criamos via um pacote nuget. Mas, por alguma razão, esse pacote não possui os filtros padrão que são incluídos quando um projeto Dynamic Data é criado via Visual Studio. Da mesma forma que os FieldTemplates, os Filters ficam na pasta DynamicData\Filters.

 

Criamos então um segundo projeto, agora utilizando o template do Visual Studio para Dynamic Data e copiamos os filtros padrão para a pasta Filters. Executando novamente nossa página, agora obtemos alguns filtros funcionando perfeitamente:

 

 

Tanto o campo bool IsActive quanto o Platform? na nossa model foram considerados, sendo gerados filtros para ambos automaticamente. A lógica de seleção de filtros é semelhante a dos FieldTemplates, ou seja, é possível criar nossos próprios filtros e selecioná-los de forma análoga, com o atributo FilterUIHint.

 

Ao contrário dos FieldTemplates, a implementação dos filtros é bastante complexa, principalmente por tratar com Expressions sobre um IQueryable não tipado. No projeto em anexo foi implementado um filtro diferenciado para Enums, onde em vez de um dropdownlist é mostrado um CheckBoxList e feito um OR com as opções selecionadas. Esse é um exemplo de filtro que está sendo utilizado em uma das páginas do M3 e que é bastante interessante, principalmente como exemplo de extensão de filtros. Alterando a nossa model para utilizar o filtro, através de [FilterUIHint(“EnumMultipleChoice”)], obtemos o seguinte resultado.

 

 

* No nosso caso a enumeração não era nullable, então essa simplificação do filtro não trata isso no momento. Ou seja, não existe a opção de filtrar pelos não definidos:

 

Ainda sobre os filtros existe um último ponto bastante interessante. Um dos nossos requisitos em uma das novas páginas da EG, que passaram a utilizar Dynamic Data, era permitir entrar na página via uma Url gerada já com parâmetros de filtro setados via queryString. No nosso caso, algo nesse estilo:

 

http://localhost:5766/Dynamic.aspx?Platform=Silverlight&IsActive=true

 

Sem alterar nada no código, essa url não tem efeito: os parâmetros são simplesmente ignorados. Após um pouco de pesquisa, tentativa e erro, chegamos a uma ótima solução para carregar automaticamente esses parâmetros da query string e popular os nossos filtros de forma a mostrar os resultados esperados.

 

Utilizando algumas chamadas a mais no nosso método de inicialização do Dynamic Data, podemos recuperar os valores selecionados da query string e repassá-los juntamente com o MetaTable para o controle, da seguinte forma:

protected void EnableDynamicData(object sender, EventArgs e)
{
    var table = MetaTable.CreateTable(typeof(Device));
    var defaultValues = table.GetColumnValuesFromRoute(HttpContext.Current);

    ((INamingContainer)sender).EnableDynamicData(typeof(Device));
    ((INamingContainer)sender).SetMetaTable(table, defaultValues);
}

A primeira chamada é simples, e apenas gera um novo MetaTable a partir das anotações na nossa classe. A segunda utiliza o comportamento padrão das rotas dinâmicas do Dynamic Data para obter os parâmetros automaticamente da query string. Finalmente, essas informações são repassadas para o nosso controle databound.

O resultado é o filtro já preenchido e os dados já filtrados ao entrar na página:

Visualização customizada

Algo que não entramos em detalhe, mas que é completamente possível, é a desativação da geração automática de colunas nas grids/detailsviews/formviews. Se fizermos isso no nosso exemplo, poderemos notar que os controles ficam vazios. O ideal é que a geração automática seja mantida, pois ela nos garante consistência na exibição  das classes utilizadas em todas as páginas. Porém, existem casos em que isso não é um problema ou que se quer customizar a visualização para uma situação específica.

A primeira forma de manter o comportamento dinâmico da UI sem a autogeração, é substituir antigos BoundField’s por DynamicField’s. Assim podemos decidir explicitamente onde colocar o campo dentro do controle databound. Setando a propriedade UIHint do DynamicField, é possível forçar o uso de um determinado template, ignorando o que estiver configurado via atributos na model.

Uma segunda alternativa, um pouco mais genérica, é através do DynamicControl, uma versão do DynamicField que pode ser utilizada dentro de TemplateFields . Ao contrário do DynamicField, que obtem seu modo de visualização do modo atual do controle externo, o DynamicControl permite configuração explícita do modo de visualização (exibição, edição e inserção), alem de todas as outras funcionalidades disponíveis no DynamicField.

Por ultimo, a forma mais flexível de todas para customizar a exibição é o DynamicEntity, que em conjunto com os controles ListView e FormView, e baseado nos templates da pasta EntityTemplates (que não foram o nosso foco nesse post), é capaz de gerar uma view 100% flexível para uma determinada classe. Através dele  é possível também configurar por exemplo um ValidationGroup para todo o controle, permitindo a edição simultânea de várias classes em uma mesma página. Na versão final do nosso exemplo, ele foi usado para a geração do trecho de inserção dos dispositivos.

Conclusão

Analisando o nosso projeto web após todas essas etapas, fica evidente o grande ganho, tanto em produtividade quanto em robustez, eficiência e simplicidade. Com pouquíssimas linhas de código, e de forma quase completamente declarativa, criamos uma página bem complexa, com filtros e campos 100% autogerados baseados em convenções claras e anotações na nossa model, que por sua vez servem também para a geração do schema do banco de dados via Entity Framework. Nas situações em que precisamos de um comportamento fora do padrão, o framework nos possibilitou customização de todas as etapas do processo.

Por fim, aos que pretendem começar uma nova solução utilizando Web Forms, fica a forte sugestão de considerar a adoção de Asp.Net Dynamic Data, pelos diversos pontos positivos que já citamos até aqui. Aos que, como nós, já possuem uma solução em andamento com código legado, é importante levar em conta o impacto das alterações e, talvez, optar pelo método incremental, como nós fizemos. Independentemente da forma escolhida, a nossa experiência mostra que o custo inicial de aprendizagem dessa nova técnica se paga rapidamente quando mudanças que antes levariam dias para serem implementadas levam, literalmente, minutos.

Escrito por Juliano Gonçalves