ASP .NET MVC - Usando Autenticação, imagens, Repositório, Unit of Work e Entity Framework (C#) - I


Neste artigo vou mostrar como usar em uma aplicação ASP .NET MVC alguns conceitos básicos que já foram abordados por mim em outros artigos.

A ideia é mostrar como aplicar os conceitos em uma aplicação ASP .NET MVC 3 na qual iremos gerenciar alunos e cursos.

Nesta aplicação além de abordar os conceitos do ASP .NET MVC versão 3 iremos ver:

  1. ASP .NET MVC com Razor
  2. Autenticação
  3. Validação
  4. Exibição de imagens
  5. Utilização do Entity Framework - Code First
  6. Aplicação do padrão Repositório
  7. Aplicação do padrão de Unit Of Work
  8. Envio de Email
  9. Tratamento de erros

A aplicação em si é bem simples de forma a tornar possível a apresentação de todos esses conceitos de forma didática.

Iremos gerenciar os alunos e cursos de uma escola em uma aplicação ASP .NET MVC.

Os recursos usados no projeto que iremos criar serão:

Isso posto vamos ao que interessa...

Criando o projeto ASP .NET MVC 3

Abra o Visual Web Developer 2010 Express Edition e no menu File clique em New Project e a seguir selecione os templates:

Em seguida selecione Internet Application e o engine Razor e clique em OK;

Razor é nome da nova View Engine das aplicações ASP .NET MVC e WebMatrix. (Veja as referências...)

De forma bem objetiva Razor foi criado para simplificar a codificação nas aplicações ASP .NET , pois usando a sintaxe do Razor o código
fica mais fácil de digitar, mais simples e legível.
(Finalmente podermos abolir o uso  das tags <%= %> no código)

O Razor é bastante 'esperto' e possui um parse que conhece as marcações HTML , a sintaxe da linguagem VB .NET (VBHTML) e C# (CSHTML).

Fique atento que o Razor não é uma nova linguagem; você vai usar os seus conhecimentos d VB .NET ou C# para usar o Razor e não o contrário.

Assim podemos enumeras o seguintes benefícios em usar o Razor:

• A sintaxe Razor é limpa e concisa, o que requer um número mínimo de digitação;
• O Razor é fácil de aprender, em parte porque ele é baseado em linguagens existentes, como C # e Visual Basic;
• O Visual Studio inclui o IntelliSense e colorização de código para a sintaxe Razor;
• As views Razor podem ser testadas de forma unitária sem exigir que você execute o aplicativo ou abra um servidor web;

Na janela Solution Explorer teremos a solução criada com a estrutura pronta para ser usada. Essa estrutura é a padrão e vamos usá-la em nosso projeto.

Definindo as referências do projeto

Antes de iniciarmos a construção do nosso projeto temos que incluir algumas referências em nossa solução de forma a podermos usar os recursos do Entity Framework.

Clique com o botão direito sobre o nome da solução e a seguir escolha Add Reference;

Depois clique na guia Browse e localize o local onde esta o arquivo EntityFramework.dll (versão 4.1 ou superior);

Obs :Se você não possui o Entity Framework 4.1 instalado baixe aqui: http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=8363

Selecione o arquivo e clique em OK;

Repita o procedimento acima e clique na guia .NET selecionando a referência ao System.ComponenteModel.DataAnnotations;

Com isso já podemos prosseguir com o nosso projeto.

Definindo o Model

Vamos começar pela definição do nosso modelo de domínio definindo as entidades que representam Cursos e Alunos de uma escola.

Vamos usar uma ferramenta ORM que nos ajudará a tratar das informações a nível de entidades permitindo a comunicação com o banco de dados e tornando transparente o acesso aos dados e assim não teremos que nos preocupar em usar comandos SQL nem utilizar ADO .NET.

A ADO .NET Entity Framework é uma framework que abstrai o esquema de um banco de dados relacional e o apresenta como um modelo conceitual. Ela é a ferramenta OR/M da Microsoft. Além dela existe também o NHibernate outro OR/M muito popular e open source.

A ADO .NET Entity Framework foi projetado para permitir que a criação de aplicações com acesso a dados use o modelo de programação feito contra um modelo conceitual ao invés do antigo modelo de programação feito diretamente contra um banco de dados relacional. O objetivo é diminuir a quantidade de código e o tempo de manutenção necessária exigida nestas aplicações orientada a dados.

Se usarmos o EF para realizar o mapeamento criando o Entity Data Model teremos uma grande ajuda da ferramenta mas em muitos casos isso ao invés de ajudar acaba atrapalhando pois o código verboso gerado pode ser um problema para integrar com o nosso modelo ou mesmo transmitir usando Web Services.

Com o Entity Framework podemos definir nossas entidades e o mapeamento entre o modelo orientado a objetos e o modelo entidade relacionamento via código usando classes POCO e a definição de um contexto via DBContext.

POCO - Plain Old CLR Object - são classes simples de domínio que possuem apenas get/sets e um construtor e que não dependem de nenhum framework; as classes POCO não são obrigadas a herdar de nenhuma outra classe ou implementar nenhuma interface.Portanto as classes POCO  são independente de frameworks.

Vamos então mostrar como usar POCO sem criar o Entity Data Model e também usar o Code-First gerando o banco de dados e as tabelas a partir de nossas entidades.

O nosso modelo de negócio consiste de duas classes:

O modelo de classes representa um Curso que possui 1 ou mais alunos.

Temos aqui uma associação que representa o relacionamento um-para-muitos.

O relacionamento um-para-muitos é usado quando uma entidade A pode se relacionar com uma ou mais entidades B.  Este relacionamento é representado pelo sinal: 1:N ou 1: * que expressa a cardinalidade do relacionamento.

Vamos então criar as classes Curso e a classe Aluno na pasta Models.

1- Criando entidade Curso:

Selecione a pasta Models e no menu Project clique em Add Class a seguir informe o nome Curso.cs e digite o código abaixo:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;

namespace Repositorio_EF.Models
{
    [Table("Cursos")]
    public class Curso
    {
        public int CursoId { get; set; }
       
        [Required(ErrorMessage = "O campo Descricao é obrigatório.")]
        public string Descricao { get; set; }

        [Required(ErrorMessage = "O campo Duracao é obrigatório.")]
        public int Duracao { get; set; }
       
        public virtual ICollection<Aluno> Alunos { get; set; }
    }
}
A classe Curso representa a entidade Curso e nela temos as definições
feitas via DataAnnotations que definem:
  • O nome da tabela correspondente gerada como sendo Cursos;
  • Os campos Descricao e Duracao são obrigatórios

A Annotation [Required(ErrorMessage = "msg")]> define que
o campo Nome é obrigatório;

Como um curso pode possuir um ou mais alunos temos a definição
de uma coleção de Alunos;

 

Os atributos Data Annotation foram introduzido no .NET 3.5 como uma forma de adicionar a validação para as classes usadas por aplicações ASP.NET. Desde aquela época, o RIA Services começou a usar anotações de dados e eles agora fazem parte do Silverlight também. No EF 4.1 o Code-First permite que você construa um EDM usando código (C#/VB .NET) e também permite realizar a validação com atributos Data Annotations.

Para este recurso devemos usar o namespace System.ComponentModel.DataAnnotations pois é ele que provê atributos de classes (usados para definir metadados) e métodos que podemos usar em nossas classes para alterar as convenções padrão e definir um comportamento personalizado que pode ser usado em vários cenários.

2- Criando entidade Aluno:

Selecione a pasta Models e no menu Project clique em Add Class a seguir informe o nome Aluno.cs e digite o código abaixo:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;

namespace Repositorio_EF.Models
{
    [Table("Alunos")]
    public class Aluno
    {
        public int AlunoId { get; set; }
        [Required(ErrorMessage = "O campo Nome é obrigatório.")]
        public string Nome { get; set; }
        [Required(ErrorMessage = "O campo Email é obrigatório.")]
        public string Email { get; set; }
        [Required(ErrorMessage = "O campo Nascimento é obrigatório.")]
        public string Nascimento { get; set; }
        [Required(ErrorMessage = "O campo Sexo é obrigatório.")]
        public string Sexo { get; set; }
        public int CursoId { get; set; }
        [InverseProperty("Alunos")]
        public virtual Curso Curso { get; set; }
    }
}
Na classe Curso continuamos a usar o DataAnnotations para definir
o nome da tabela a ser gerada para entidade: Alunos

Os campos obrigatórios : Nome,Email,Nascimento,Sexo

A propriedade Curso que representa a vinculação entre as entidades
Curso e Aluno;

A chave CursoId através da qual a vinculação é realizada;

Podemos utilizar anotações para sobrescrever a convenção padrão e assim definir os nomes das tabelas (como já fizemos), campos e outras propriedades.

A seguir temos a relação das principais Annotations suportadas pelo Entity Framework 4.1 :

KeyAttribute - Usada para especificar que uma propriedade/coluna é parte da chave primária da entidade e se aplica apenas a propriedades escalares;
StringLengthAttribute -
Usada para especificar o tamanho máximo de uma string;
ConcurrencyCheckAttribute -
Usada para especificar que uma propriedade/coluna tem um modo de concorrência "fixed " no modelo EDM;
RequiredAttribute : -
Usada para especificar que uma propriedade/coluna é não-nula e aplica-se a propriedades escalares, complexas, e de navegação;
ColumnAttribute
Usada para especificar o nome da coluna, a posição e o tipo de dados ;
TableAttribute
Usada para especificar o nome da tabela e o esquema onde os objetos da classe serão atualizados;
ForeignKeyAttribute -
Usado em uma propriedade de navegação para especificar a propriedade que representa a chave estrangeira da relação
DatabaseGeneratedAttribute -
Usada em uma propriedade para especificar como o banco de dados gera um valor para a propriedade: Identity, Computed ou None;
NotMappedAttribute
Usada para definir que a propriedade ou classe não estará no banco de dados;

Agora vamos criar uma classe chamada EscolaContext que herda de DbContext e que permite o acesso mais fácil a consultas e permite trabalhar com os dados das entidades como objetos.

O DbContext é um invólucro para ObjectContext e além disso esta classe contém:

- Um conjunto de APIs que são mais fáceis de usar do que a exposta pelo ObjectContext;
- As APIs que permitem utilizar o recurso do Code-First e as convenções;

O DbSet é um invólucro para ObjectSet.

3- Criando o Contexto

Selecione a pasta Models e no menu Project clique em Add Class a seguir informe o nome EscolaContext.cs e digite o código abaixo:

using System.Data.Entity;

namespace Repositorio_EF.Models
{
    public class EscolaContext : DbContext
    {
        public DbSet<Curso> Cursos { get; set; }
        public DbSet<Aluno> Alunos { get; set; }
    }
}
Utilizamos o padrão Code First para permitir a persistência no banco de dados.

Com isso as propriedades Cursos e Alunos serão serão mapeadas para tabelas com mesmo nome
no banco de dados.

Cada propriedade definida na entidade Aluno e Curso é mapeada para colunas das tabelas
Livros e Editoras que foram definidas via DataAnnotations;

Com apenas essas definições o banco de dados e a tabela serão criados quando da
primeira execução da aplicação e podemos configurar para que elas sejam sobrescritas
ou não a cada execução. Esse é o modelo
Code-First.

As tabelas são geradas quando criamos uma instância de DbContext() e o contexto necessitar
fazer alguma requisição ao banco de dados.

API DbContext  -  Que nada mais é do que uma abstração simplificada do ObjectContext e diversos outros tipos introduzidos nas versões anteriores do Entity Framework. Esta API é otimizada para realizar as tarefas mais comuns e dar suporte a padrões de código como "Unit Of Work Pattern" e  "Repository Pattern".

A API DbContext é um invólucro para ObjectContext e além disso contém:  Um conjunto de APIs que são mais fáceis de usar do que a exposta pelo ObjectContext e APIs que permitem utilizar o recurso do Code-First e as convenções.

A API DbContext por convenção irá criar o banco de dados para você em localhost\SQLEXPRESS e o nome do banco de dados é derivado a partir do nome do projeto e do contexto, no nosso caso Repositorio_EF.Contexto.

Podemos alterar a lógica da criação do banco de dados e das tabelas através do método de classe System.Data.Entity.Database.SetInitializer<TypeContext>.

Ao usar este método passamos as instâncias do tipo do tipo System.Data.Entity.IDatabaseInitializer.

As classes usadas para definir a estratégia de criação do banco de dados e das tabelas são:

No nosso exemplo vamos definir no arquivo Global.asax da raiz da solução o código abaixo no método Application_Start:

 protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            // Alterando a estratégia de criação do banco de dados
            Database.SetInitializer(new DropCreateDatabaseIfModelChanges<EscolaContext>());
            RegisterGlobalFilters(GlobalFilters.Filters);
            RegisterRoutes(RouteTable.Routes);
        }

Estamos adotando a estratégia de criar o banco de dados novamente caso ele ainda não exista ou caso haja alguma alteração em suas tabelas.

Aplicando o padrão Repositório

O padrão de projeto Repository acrescenta uma camada de abstração no topo da camada de consultas e ajuda eliminar lógica duplicada na implementação do código de suas consultas ao modelo de entidades.

Foi Martin Fowler que definiu o padrão Repository no seu livro - Patterns of Enterprise Application Architecture - da seguinte forma: "Intermedeia entre o domínio e as camadas de mapeamento de dados usando uma interface de coleção para acessar objetos de domínio." (numa tradução livre by Macoratti)

Um repositório é essencialmente uma coleção de objetos de domínio em memória, e, com base nisso o padrão Repository permite realizar o isolamento entre a camada de acesso a dados (DAL) de sua aplicação e sua camada de apresentação (UI) e camada de negócios (BLL).

Ao utilizar o padrão Repository você pode realizar a persistência e a separação de interesses em seu código de acesso a dados visto que ele encapsula a lógica necessária para persistir os objetos do seu domínio na sua fonte de armazenamento de dados.

Em suma, você pode usar o padrão Repository para desacoplar o modelo de domínio do código de acesso a dados.

Vamos então criar um repositório em nosso projeto definindo duas classes na pasta Models. As classes : AlunoRepositorio e CursoRepositorio.

Nessas classes vamos definir um repositório que representa o conjunto de todos os objetos do respectivo tipo: Aluno e Curso.

No padrão repositório devemos também criar os métodos para recuperar e adicionar entidades;

Nos repositórios criados poderemos trabalhar com objetos na memória ou reconstruí-los a partir do banco de dados.

1- Criando o repositório: AlunoRepositorio.cs

Selecione a pasta Models e no menu Project clique em Add Class a seguir informe o nome AlunoRepositorio.cs e digite o código abaixo:

using System;
using System.Collections.Generic;
using System.Linq;

namespace Repositorio_EF.Models
{
    public class AlunoRepositorio
    {
         private bool disposed = false;
         private EscolaContext context;
         //
         public AlunoRepositorio(EscolaContext context)
         {
            this.context = context;
         }
         //
         public void Adiciona(Aluno aluno)
         {
            context.Alunos.Add(aluno);
         }
         //
         public Aluno Busca(int id)
         {
            return context.Alunos.Find(id);
         }
         //
         public void Remove(int id)
         {
            Aluno aluno = context.Alunos.Find(id);
            context.Alunos.Remove(aluno);
         }
         //
         public List<Aluno> Alunos
         {
            get { return context.Alunos.ToList(); }
         }
         //
         public void Salva()
         {
            context.SaveChanges();
         }
         //
         protected virtual void Dispose(bool disposing)
         {
            if (!this.disposed)
            {
                if (disposing)
                {
                    context.Dispose();
                }
            }
            this.disposed = true;
         }
         //
         public void Dispose()
         {
            Dispose(true);
            GC.SuppressFinalize(this);
         }
   }
}
Definindo o repositório pretendemos melhorar a organização da nossa aplicação
diminuindo o acoplamento e a manutenção e tornando o código mais legível.

No repositório definimos os métodos:

  • Salva()
  • Remove()
  • Adiciona()
  • Busca()
  • Alunos()

Em todos os métodos estamos usando o contexto e os respectivos métodos do Entity
Framework para realizar as operações;

2- Criando o repositório: CursoRepositorio.cs

Selecione a pasta Models e no menu Project clique em Add Class a seguir informe o nome CursoRepositorio.cs e digite o código abaixo:

using System;
using System.Collections.Generic;
using System.Linq;

namespace Repositorio_EF.Models
{
    public class CursoRepositorio : IDisposable
    {
        private bool disposed = false;

         private EscolaContext context;
         //
         public CursoRepositorio(EscolaContext context)
         {
            this.context = context;
         }
         //
         public void Adiciona(Curso curso)
         {
            context.Cursos.Add(curso);
         }
         //
         public List<Curso> Cursos
         {
            get
            {
               return context.Cursos.ToList();
            }
         }
         //
         public Curso Busca(int id)
         {
            return context.Cursos.Find(id);
         }
         //
         public void Remove(int id)
         {
             Curso curso = Busca(id);
             context.Cursos.Remove(curso);
         }
         //
         public void Salva()
         {
             context.SaveChanges();
         }

         protected virtual void Dispose(bool disposing)
         {
            if (!this.disposed)
            {
                if (disposing)
                {
                    context.Dispose();
                }
            }
            this.disposed = true;
         }

         public void Dispose()
         {
            Dispose(true);
            GC.SuppressFinalize(this);
         }
    }
}

No repositório definimos os métodos:

  • Salva()
  • Remove()
  • Adiciona()
  • Busca()
  • Cursos()

Em todos os métodos estamos usando o contexto
e os respectivos métodos do Entity Framework para
realizar as operações;

Aplicando o padrão Unit of Work

O padrão Unit of Work esta presente em quase todas as ferramentas OR/M atuais (digo quase pois não conheço todas) e geralmente você não terá que criar a sua implementação personalizada a menos que decida realmente fazer isso por uma questão de força maior.

Dessa forma a interface ITransaction no NHibernate, a classe DataContext no LINQ to SQL e a classe ObjectContext no Entity Framwork são exemplos de implementações do padrão Unit of Work. (Até o famigerado DataSet  pode ser usado como uma Unit of Work.)

Então o padrão Unit of Work pode ser visto como um contexto, sessão ou objeto que acompanha as alterações das entidades de negócio durante uma transação sendo também responsável pelo gerenciamento dos problemas de concorrência que podem ocorrer oriundos dessa transação.

Como usar o padrão Unit of Work ?

Uma das melhores maneiras de usar o padrão Unit of Work é permitir que classes e serviços diferentes façam parte em uma única transação lógica sem se conhecerem mutuamente.

Em uma das implementações do padrão repositório podemos começar definindo uma interface (da mesma forma que no padrão repository) ou definindo uma classe concreta.

Selecione a pasta Models e no menu Project clique em Add Class a seguir informe o nome UnitOfWork.cs e digite o código abaixo:

conforme mostrado abaixo:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace Repositorio_EF.Models
{
    public class UnitOfWork
    {
         private bool disposed = false;
         private EscolaContext context = new EscolaContext();
         private CursoRepositorio cursoRepositorio;
         private AlunoRepositorio alunoRepositorio;

         public AlunoRepositorio AlunoRepositorio
         {
             get
             {
                 if (alunoRepositorio == null)
                 {
                    alunoRepositorio = new AlunoRepositorio(context);
                 }
                 return alunoRepositorio;
             }
         }

         public CursoRepositorio CursoRepositorio
         {
            get
            {
                 if (cursoRepositorio == null)
                 {
                    cursoRepositorio = new CursoRepositorio(context);
                 }
                 return cursoRepositorio;
            }
         }

         public void Salva()
         {
            context.SaveChanges();
         }

         protected virtual void Dispose(bool disposing)
         {
            if (!this.disposed)
            {
                if (disposing)
                {
                    context.Dispose();
                }
            }
            this.disposed = true;
         }

         public void Dispose()
         {
            Dispose(true);
            GC.SuppressFinalize(this);
         }
     }
}
Nosso objetivo ao criar a classe UnitOfWork é compartilhar o mesmo
DBContext entre nossos repositórios.

Por isso criamos o método Salva() para cada repositório.

Definindo a classe para exibir imagens

Vamos agora criar a classe ImageResult que irá permitir que exibamos imagens em nossa aplicação.

Selecione a pasta Models e no menu Project clique em Add Class a seguir informe o nome ImageResult.cs e digite o código abaixo:

using System.IO;
using System.Web;
using System.Web.Mvc;
using System;

public class ImageResult : ActionResult
{

    private string _path;
    public ImageResult(string path)
    {
        _path = path;
    }

    public override void ExecuteResult(ControllerContext context)
    {
        byte[] bytes = null;

        //sem contexto ? para o processamento.
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }

        //verifica o arquivo
        if (File.Exists(_path))
        {
            bytes = File.ReadAllBytes(_path);
        }
        else
        {
            throw new FileNotFoundException(_path);
        }

        //determina o conteúdo
        string contentType = GetContentTypeFromFile();

        HttpResponseBase response = context.HttpContext.Response;
        response.ContentType = contentType;
        MemoryStream imageStream = new MemoryStream(bytes);
        byte[] buffer = new byte[4096];

        while (true)
        {
            int read = imageStream.Read(buffer, 0, buffer.Length);

            if (read == 0)
            {
                break; // TODO: might not be correct. Was : Exit While
            }

            response.OutputStream.Write(buffer, 0, read);
        }
        response.End();
    }

    private string GetContentTypeFromFile()
    {
        //pega a extensão a partir do caminho para determinar o conteudo
        string[] parts = _path.Split('.');
        string extension = parts[parts.Length - 1];
        string contentType = null;

        switch (extension.ToLower())
        {
            case "jpeg":
            case "jpg":
                contentType = "image/jpeg";
                break; //
            case "gif":
                contentType = "image/gif";
                break; //
            default:
                throw new NotImplementedException(extension + " não tratada");
        }
        return contentType;
    }
}

No código acima temos:

Veja a continuação em : ASP .NET MVC - Usando Autenticação, imagens, Repositório, Unit of Work e Entity Framework (C#) - II

Rom 7:22 Porque, segundo o homem interior, tenho prazer na lei de Deus;
Rom 7:23
mas vejo nos meus membros outra lei guerreando contra a lei do meu entendimento, e me levando cativo à lei do pecado, que está nos meus membros.
Rom 7:24
Miserável homem que eu sou! quem me livrará do corpo desta morte?

Rom 7:25
Graças a Deus, por Jesus Cristo nosso Senhor! De modo que eu mesmo com o entendimento sirvo à lei de Deus, mas com a carne à lei do pecado.

Referências:


José Carlos Macoratti