ASP.NET MVC - Desenvolver ficou fácil (pt-BR)
Introdução
O intuito deste artigo é criar uma aplicação completa com o ASP.NET MVC 3, tendo como base várias dúvidas que recebo sobre a tecnologia e também mostrar que desenvolver utilizando o ASP.NET MVC 3 também é fácil.
Desta forma, este post exemplificará os principais pontos no desenvolvimento de uma aplicação com ASP.NET MVC 3 (Incluindo o Tools Update) e o ADO.NET Entity Framework 4.1.
Cenário de uso deste caso
Durante o desenvolvimento da aplicação, recursos de produtividade como Scaffold Templates serão apresentados, e em cenários mais complexos com repositório de dados, injeção de dependência e etc, este exemplo não funcionará 100%, tornando-se necessária a edição do código gerado pelas ferramentas.
Sendo assim, este cenário se aplica para casos onde temos um domínio mais simples, e não necessitamos de recursos como DI, IoC e etc.
Estruturando a aplicação
A organização de uma aplicação é indispensável, seja ela na fase de desenvolvimento ou na fase de suporte. Se tens uma aplicação bem estruturada e organizada, pode-se identificar e resolver problemas isoladamente e mais facilmente, e o mesmo vale para o desenvolvimento.
Toda esta organização começa já na criação da solução (Solution) e organização dos projetos dentro da mesma.
Particurlarmente, organizo a aplicação como descrito na Figura 1 e Figura 2 abaixo, e isto varia de pessoa a pessoa e empresa a empresa.
http://weblogs.asp.net/blogs/andrebaltieri/image_thumb_4A92EB99.png
Figura 1 – Criando uma solução em branco.
Deste modo, a solution conterá 3 projetos, sendo eles:
- MvcFakeStore.Data (Adicionar pasta “Contexts”)– Class Library
- MvcFakeStore.Domain (Adicionar pasta “Entities”) – Class Library
- MvcFakeStore.Web – ASP.NET MVC 3 Web Application (Configurado como na Figura 3)
Ao término das adições, a solução fica como mostrado na Figura 2.
http://weblogs.asp.net/blogs/andrebaltieri/image_thumb_31BBC6A9.png
Figura 2 – Solução com projetos criados.
http://weblogs.asp.net/blogs/andrebaltieri/image_thumb_2B999B70.png
Figura 3 – Configurações do projeto MvcFakeStore.Web.
Uma aplicação tem várias responsabilidades e divisões. Por exemplo, o acesso à dados é uma área de responsabilidade, as regras de negócio outra, e sendo assim, possuir um projeto separado para cada uma destas áreas pode facilitar o desenvolvimento e manutenção da aplicação.
Desta forma, temos as seguintes separações:
- Acesso à Dados: Representado pelo projeto MvcFakeStore.Data, que terá os contextos, inicializadores e etc.
- Domínio: Representado pelo projeto MvcFakeStore.Domain, que terá as classes representando as regras de negócio da aplicação.
- Interface: Representada pelo projeto MvcFakeStore.Web, que será a aplicação criada com ASP.NET MVC que o usuário acessará para manipular os dados.
Criando o Contexto
O contexto (Ou Data Context) será responsável pelo gerenciamento das sessões no banco de dados, mapeamento das tabelas para as classes e operações como leitura, escrita, alteração e exclusão de dados (Famos CRUD).
Atualmente, fala-se muito sobre orientação à objetos, DDD, ORMs, mas qual a grande vantagem disso? Antes usávamos DataSets e tudo era mais simples. De fato talvez fosse mais simples, mas junto aos dados do DataSet existia um XML que descrevia o que cada campo fazia, seu tamanho e outras informações, ou seja, era um banco de dados replicado na aplicação.
Se temos todas estas definições já em nossas classes, não há esta necessidade, tornando assim objetos algo muito mais leve para se trafegar entre as camadas da aplicação. Existem outras inúmeras vantagens no uso de tráfego de objetos ao invés de DataSet (ou DataTable) entre camadas, mas estenderia muito este post.
Outro ponto é a constante mudança de escopo que sofremos durante o andamento do projeto, tornando mudanças no código algo frequente, resultado de falhas na análise de requisitos.
Pensando em uma solução para este problema (Ninguém faz uma análise de requisitos perfeita), o correto é primeiramente ter um ponto único de atualização das regras de negócio no sistema, e isto é feito no domínio (Domnain), onde as classes que refletem o mundo real serão criadas. Partindo desta linha de raciocínio, temos o DDD, onde o desenvolvimento começa sempre pelo domínio (Criação das classes), e mantém o foco sempre nas regras de negócio.
Para operações no banco de dados, o uso de Stored Procedures é comum, porém as Stored Procedures também contém regras de negócio, e se dissemos que criar um único ponto de atualização (Princípio DRY), fica inviável ter que atualizar as classes da aplicação e também as Stored Procedures.
O uso de ORM se encaixa justamente neste ponto, onde teremos o mapeamento de cada tabela do banco para uma classe na aplicação e as operações CRUD sendo criadas em tempo de execução e de acordo com as regras de negócio definidas no domínio da aplicação, poupando assim tempo de desenvolvimento e foco no domínio.
Dentro deste contexto apresentado, algo que sempre preocupa um time é o “custo de desenvolvimento” (Tempo) que algo leva para ser construído. Não adiantaria também colocar todos estes pontos acima citados em prática se os mesmos tomassem um tempo que a equipe não possúi.
Mapear todas as tabelas para as classes por exemplo seria algo trabalhoso (E chato), pois teria que criar um script para gerar o banco de dados e posteriormente as classes.
Por estes motivos que temos os Frameworks ORM, que já fazem tudo isto sozinho (Mapeamento, criação de banco de dados, operações CRUS e etc.). Para este post utilizarei o Framework ORM da Microsoft, o ADO.NET Entity Framework, que se encontra na versão 4.1.
O ADO.NET Entity Framework 4.1 já se encontra disponível no gerenciador de pacotes NuGet porém se estiver utilizando o ASP.NET MVC 3 já encontrará a opção “Add Package Library Reference” no projeto.
Como descrito previamente, o contexto será alocado no projeto MvcFakeStore.Data, na pasta Contexts, então será necessário adicionar as referências ao mesmo, como na Figura 4.
http://weblogs.asp.net/blogs/andrebaltieri/image_thumb_5FC9C8A7.png
Figura 4 – Adicionando um novo pacote ao projeto MvcFakeStore.Data.
Quando a janela de pacotes abrir, realize as operações como na Figura 5.
http://weblogs.asp.net/blogs/andrebaltieri/image_thumb_1CAB1DEF.png
Figura 5 – Instalando o pacote Entity Framework.
Nota: É importante sempre verificar a versão do pacote que está sendo instalada, como mostrado na Figura 5.
Nota: O gerenciador de pacotes utiliza o PowerShell 2.0, caso não possua (Meu caso com Windows XP SP3), baixe-o aqui.
O erro recebido ao instalar o pacote quando não tem o Power Shell instalado está na Figura 6.
http://weblogs.asp.net/blogs/andrebaltieri/image_thumb_04A9CE38.png
Figura 6 –Erro ao instalar pacotes devido a falta do Windows Power Shell 2.0.
Com as referências adicionadas, criaremos duas classes:
- MvcFakeStoreContext – Gerenciará o contexto da aplicação.
- MvcFakeStoreContextInitializer – Gerenciará o inicio deste contexto e refletirá as atualizações do contexto no banco de dados.
A Figura 7 ilustra a classe MvcFakeStoreContext.cs.
http://weblogs.asp.net/blogs/andrebaltieri/image_thumb_501FAB7C.png
Figura 7 – Classe MvcFakeStoreContext.cs.
Nesta classe, importamos o namespace System.Data.Entity, responsável por manipular e mapear as futuras entidades do contexto, e em seguida herdamos do objeto DbContexto, que possui por exemplo os métodos CRUD.
Precisamos agora de um inicializador, que aplicará as mudanças do contexto no banco de dados (Não queremos ficar criando scripts SQL para tudo que alterarmos no domínio).
A Figura 8 ilustra a esta classe.
http://weblogs.asp.net/blogs/andrebaltieri/image_thumb_1EB8E93C.png
Figura 8 – Classe MvcFakeStoreContextInitializer.cs.
Note que temos a herança de um objeto chamado “DropCreateDatabaseIfModelChanges” (Refazer o banco de dados se o domínio mudar), e poderiamos ter também o “DropCreateDatabaseAlways”, que re-criaria o banco toda vez que a aplicação rodasse.
Além disso, ainda podemos sobrescrever alguns métodos nesta classe, mas faremos isto depois.
Criando o Domínio
O domínio é responsável pelas regras de negócio da aplicação, que serão representadas por objetos (Classes), e farão uso dos contextos para persistirem seus estados no banco de dados.
Para simplificar este exemplo, criarei duas classes, sendo uma para persistir os produtos e outra para as categorias.
As Figuras 9 e 10 exemplificam as classes mencionadas.
http://weblogs.asp.net/blogs/andrebaltieri/image_thumb_0D0100C4.png
Figura 9 – Classe Product.cs.
http://weblogs.asp.net/blogs/andrebaltieri/image_thumb_790C8F8F.png
Figura 10 – Classe Category.cs.
Até o momento somente classes simples foram criadas, e por hora elas não possuem relacionamento, validação e também não estão sendo mapeadas a nenhuma tabela do banco de dados (Que não foi criado ainda).
Relacionando as classes
O próximo passo é relacionar estas classes, aplicando assim regras de negócio à elas. Exemplo:
- Uma categoria pode conter muitos produtos;
- Um produto possúi apenas uma categoria;
Poderiamos criar relacionamentos mais complexos, de NxN, como mostrado aqui neste post: ASP.NET MVC e EF–Relacionamento N–> N.
Para realizar estes relacionamentos é simples. Na classe Product.cs, criaremos uma propriedade do tipo inteiro, que representará o Id de uma categoria, e também criaremos um objeto do tipo Category, que representa a categoria cujo Id foi preenchido que que irá conter todos os dados (Não somente o Id) da categoria.
Da mesma forma, na categoria, teremos uma lista de produtos, que serão os produtos que pertencem a esta categoria.
Estas ligações (Objeto categoria na classe produto e lista de produtos na classe categoria) são feitas para facilitar ocarregamento e manipulação de dados no domínio, tornando possível a navegação também nos objetos filhos.
Dúvida: “Se eu tenho um objeto Nota Fiscal, que possúi 10 mil itens e cada item possúi uma composição de 10 ou mais peças, isto quer dizer que estarei carregando por volta de 100.000 objetos na memória ao carregar uma Nota Fiscal?”
Resposta: Não necessáriamente. O Entity Framework possúi métodos inteligentes para carregar dados, onde um objeto filho só é carregado quando invocado. É o que chamamos de Lazy Load. Há um tempo atrás escrevi um artigo bem robusto sobre EF para o Linha de Código, acesse-o aqui: Descubra o ADO.NET Entity Framework.
Deste modo, as classes serão modificadas como nas Figuras 11 e 12.
http://weblogs.asp.net/blogs/andrebaltieri/image_thumb_47399A5A.png
Figura 11 – Classe Product.cs com relacionamento para classe Category.cs.
http://weblogs.asp.net/blogs/andrebaltieri/image_thumb_1C19AEA8.png
Figura 12 – Classe Category.cs com referência a classe Product.cs.
Em adicional temos o modificador Virtual em ambas propriedades que são de tipos complexos, isto permite que o EF sobrescreva estas propridades em tempo de execução e as preencha.
Decorando as classes
No início do post falei sobre DRY (Don’t Repeate Yourself) e as validações se encaixam bem neste princípio. Quando temos a regra de negócio toda no domínio, o ideal é que a validação dos dados também esteja lá. As validações também são regras de negócio, e se estamos concetrando as regras de negócio no domínio, criá-las na interface quebraria este escopo.
O .NET Framework 4 conta com um recurso interessante, chamado de Data Annotations, que eu mostrei neste post: ASP.NET MVC–Validação de dados [Modelo].
O ato de “decorar” as classes é a aplicação de atributos que tornam as classes mais ricas, fugindo do padrão ânemico de criação de classes como quando trabalhavamos com stored procedures.
Os DataAnnotations possúem vários marcadores, porém não abordarei todos aqui. Para lista dos marcadores, consulte este post: MSDN Namespace System.ComponentModel.DataAnnotations.
Por padrão não temos referência aos DataAnnotations, então será preciso adicionar a referência ao System.ComponentModel.DataAnnotations no projeto MvcFakeStore.Domain, como mostrado na Figura 13.
http://weblogs.asp.net/blogs/andrebaltieri/image_thumb_28AC7D26.png
Figura 13 – Adicionando referência aos DataAnnotations.
Com a referência adicionada, decore as classes como abaixo:
http://weblogs.asp.net/blogs/andrebaltieri/image_thumb_46AFAC7C.png
Figura 14 – Classe Product.cs decorada.
http://weblogs.asp.net/blogs/andrebaltieri/image_thumb_36C819CB.png
Figura 15 – Classe Category.cs decorada.
Note que para os campos Virtual não coloquei como requerido, pois os mesmo serão preenchidos dinamicamente e sendo assim nunca seriam válidos.
Mapeamento das classes
Com os relacionamentos feitos e as classes decoradas, precisamos “mapear” as classes, que seria relacionar cada campo dela com um campo de uma tabela no banco de dados. Mas nem banco de dados temos ainda!!!
Então deixa que o Entity Framework faz todo trabalho pra gente. Tudo que precisamos fazer é voltar ao contexto e criar os DbSets (Conjuntos de entidades) referente a cada classe que queremos persistir no banco. Simples né =)
Para isso, modifique a classe MvcFakeStoreContext.cs (Não se esqueça de adicionar a referência ao projeto MvcFakeStore.Domain, pois as entidades estão lá) como indicado na Figura 16.
http://weblogs.asp.net/blogs/andrebaltieri/image_thumb_4767C6CC.png
Figura 16 – Adicionando os DbSets para as classes do domínio.
Por fim, para nossa classe MvcFakeStoreContextInitializer.cs (Nome pequeno né) não ficar vazia e até mesmo para exemplificar seu uso mais afundo, vamos sobrescrever o método Seed, que será resposável por alimentar a base de dados com alguma informação sempre que o banco fore recriado.
http://weblogs.asp.net/blogs/andrebaltieri/image_thumb_6E5A8861.png
Figura 17 – Método Seed do inicializador de contextos.
Criando os Controllers e Views com Scaffolding Templates
Com o contexto e domínio criado, chega a hora de criar a interface (#TODOSCHORA). O ASP.NET MVC 3 Tools Update trouxe uma atualização incrível, onde todo código do controller e das views são criados automaticamente pelo Visual Studio, e para quem diz que tudo gerado veria uma “caixa-preta”, todo o código gerado pelos templates pode ser editado, desde o C# até o HTML com Razor;
Antes de criar os controllers e views, não se esqueça de adicionar ao projeto MvcFakeStore.Web referências aos projetos MvcFakeStore.Domain e MvcFakeStore.Data. Também será necessário dar um build na aplicação.
Com as referências adicionadas, clique com o botão direito sobre a pasta Controllers e selecione a opção Add -> Controller. A tela da Figura 18 se abrirá.
http://weblogs.asp.net/blogs/andrebaltieri/image_thumb_5901F45B.png
Figura 18 – Configurando o template para criação do Controller e Views do produto.
Após clicar em Add, note que vários arquivos foram gerados, sendo eles um Controller (ProductController.cs e uma view para cada Action do controller, como mostrado na Figura 19.
http://weblogs.asp.net/blogs/andrebaltieri/image_thumb_745EF8B1.png
Figura 19 – Arquivos gerados pelo template.
Repita o passo com a classe Category, como mostrado na Figura 20.
http://weblogs.asp.net/blogs/andrebaltieri/image_thumb_30ACDEFE.png
Figura 20 – Configurando o template para a categoria.
- Controller Name: Nome do controller, sempre no formato <prefixo> + Controller;
- Template: Temos três opções de template, sendo elas, vazio, com métodos CRUD já feitos e baseados no Entity Framework e com os métodos CRUD vazios.
- Model Class: Classe na qual as views irão se basear.
- Data Context: Contexto que criamos no projeto MvcFakeStore.Data.
- Views: Razor ou ASPX.
Nota: A validação já está toda feita =D
http://weblogs.asp.net/blogs/andrebaltieri/image_thumb_41B62E43.png
Rodando a aplicação
Antes de rodar a aplicação temos que acertar alguns pontos:
- ConnectionString: Que indicará qual servidor a aplicação se conectará e criará o banco de dados (Sim, o banco será criado automaticamente).
- Global.asax: É o responsável por gerenciar o ciclo de vida da aplicação, então ao iniciar a aplicação informaremos o nosso inicializador de contextos (MvcFakeStoreContextInitializer).
- Links para a sessão de produtos e categorias.
- Setar o projeto Web como projeto inicial.
Para adicionar a connection string, abra o arquivo Web.Config da raiz do projeto MvcFakeStore.Web (Cuidado pois temos vários arquivos web.config em outras pastas como a pasta Views. Atente-se para abrir o da raiz), e adicione a connection string abaixo:
http://weblogs.asp.net/blogs/andrebaltieri/image_thumb_12F59703.png
Figura 21 – Adicionando a connection string ao arquivo Web.Config.
Note que a connection string deve ter o mesmo nome do contexto que criamos (MvcFakeStoreContext).
Para o Global.asax, adicione as referèncias aos namespaces System.Data.Entity e MvcFakeStore.Data.Contexts e no método Application_Start atribua o inicializador, como na Figura 22.
http://weblogs.asp.net/blogs/andrebaltieri/image_thumb_085A5A52.png
Figura 22 – Atribuindo o inicializador de contextos no Global.asax.
Para “dar um tapa” no visual, altere o título da página e crie mais dois links (Um para produto e outro para categoria) na página inicial (_Layout.cshtml), como mostrado na Figura 23.
http://weblogs.asp.net/blogs/andrebaltieri/image_thumb_593020CD.png
Figura 23 – Alterando o layout inicial da aplicação.
Por fim, torne o projeto MvcFakeStore.Web como o projeto inicial da solução, como mostrado na Figura 24.
http://weblogs.asp.net/blogs/andrebaltieri/image_thumb_4BEEB91C.png
Figura 24 – Tornando o projeto MvcFakeStore.Web o projeto inicial da solução.
Depois deste passo, é só rodar a aplicação que terá este resultado:
- Na página inicial temos o nome que alteramos, os links que criamos e um indicador marcando que não estmos logados na aplicação, como mostrado na Figura 25.
http://weblogs.asp.net/blogs/andrebaltieri/image_thumb_23E12B5F.png
Figura 25 – Tela inicial. - Na página de produtos não temos nenhum produto.
- Na página de categorias já temos uma categoria cadastrada, que é aquela adicionada no método Seed da classe MvcFakeStoreContextInitializer, como mostrado na Figura 26.
http://weblogs.asp.net/blogs/andrebaltieri/image_thumb_3B9D8427.png
Figura 26 – Tela de categorias.
Autenticação e Autorização
Para finalizar com chave de ouro, como notamos, já possúimos um certo vínculo com o esquema de autenticação e autorização padrão do ASP.NET (Vide o identificador na Figura 25).
Deste modo, utilizaremos o membership do ASP.NET para autenticar e autorizar usuários na aplicação.
O primeiro passo é executar o configurador do ASP.NET, como mostrado no Figura 27.
http://weblogs.asp.net/blogs/andrebaltieri/image_thumb_6053BD00.png
Figura 27 – Executando o configurador do ASP.NET.
Quando o site de configuração abrir, na aba Security, clique na opção Enable Roles. Deste modo poderemos criar perfis de usuários, e é isto que faremos, criando os três perfis abaixo:
- Usuário – Acesso de leitura
- Funcionário – Leitura, Edição e Escrita.
- Administrador – Leitura, Edição, Escrita e Exclusão.
Adicione então os três roles clicando dobre o link “Create or Manage Role”, como mostrado na Figura 28.
http://weblogs.asp.net/blogs/andrebaltieri/image_thumb_75D61DBD.png
Figura 28 – Criação dos roles.
De volta a aba Security, criaremos três usuários para testar, como mostrado na Figura 29.
http://weblogs.asp.net/blogs/andrebaltieri/image_thumb_7B49AFC3.png http://weblogs.asp.net/blogs/andrebaltieri/image_thumb_401AF55A.png http://weblogs.asp.net/blogs/andrebaltieri/image_thumb_27B0035F.png
Figura 29 – Criando os usuários e atrelando-os aos perfis.
Autorizando as ações dos Controllers
Após os usuários criados, aplicaremos as ações nos controllers, tudo através de notações, legível e simples, como mostrado nas Figuras 30 e 31.
http://weblogs.asp.net/blogs/andrebaltieri/image_thumb_65D0D023.png
Figura 30 – Decorando o controller ProductController.cs
Note que informamos no método Authorize, os Roles separados por virgula que podem acessar aquela ação. Como o método index lista todos os produtos, não requeri nenhum tipo de autorização/autenticação á ele, permitindo que qualquer usuário o acesse.
Já para o método Delete, eu informo que somente administradores podem exlcuir um produto.
O mesmo vale para a Figura 31, que exemplifica o controller CategoryController.cs.
http://weblogs.asp.net/blogs/andrebaltieri/image_thumb_6AD82F34.png
Figura 31 – CategoryController.cs decorado com autorizações.
Ufa, e assim termina a aplicação. Ainda tem rotas e areas, mas fica para um próximo artigo. Espero que este seja útil.
Conclusão
Em pouco tempo e com pouco conhecimento podemos criar aplicações simples e até arriscar algumas aplicações mais complexas. O ASP.NET MVC junto com o EF são muito produtivos e apesar do ASP.NET MVC gerar boa parte (Para não dizer todo) do código, temos total liberdade para alterá-lo.
Por fim, temos recursos como Membership, herdado do ASP.NET que facilitam ainda mais a nossa vida.
Downloads
André Baltieri
MTAC – Microsoft Technical Audience Contributor
MSN: andrebaltieri@hotmail.com | Twitter:
@andrebaltieri
Blog: http://andrebaltieri.wordpress.com
Inside .NET Users Group Leader
http://www.insidedotnet.com.br/