ASP .NET MVC

Utilizando o Micro ORM Dapper em uma aplicação ASP.NET Core

Persistir informações da aplicação é um requisito básico de quase todos os sistemas desenvolvidos atualmente. E o meio mais utilizado para isso são os bancos de dados relacionais.

Porém, o processo de persistência de dados em um banco de dados relacional não é simples, são necessárias uma gama de configurações, além de certas adaptações para que os objetos sejam persistidos corretamente no banco.

Para facilitar este processo, foram criados os frameworks ORM (Object-Relational Mapping – mapeamento objeto relacional). Eles se encarregam do trabalho pesado, como o processo de conversão dos objetos da aplicação em informações compreendidas pelo banco de dados (códigos SQL), e abstraem o máximo do acesso ao banco para facilitar a persistência de dados. Com isso, o desenvolvedor pode focar no que é mais importante para a aplicação.

Só que, com a facilidade, esses frameworks ORM acabaram comprometendo um pouco da performance das aplicações. Para resolver este problema, criou-se os chamados micro frameworks ORM. Este tipo abstrai apenas o necessário e otimiza ao máximo. Um dos frameworks que se destacam nesta categoria é o Dapper.NET.

Criado pela equipe por atrás do Stack Overflow, este micro framework ORM foi criado visando rapidez, fácil integração e liberdade de configuração. Com poucas linhas de código é possível adicioná-lo em uma aplicação e já fazer uso de todo o seu poder.

Para se ter ideia da sua rapidez e das características que o caracterizam, podemos ver a comparação que a sua equipe realiza com outros frameworks ORM disponíveis para .NET:

C# (C Sharp) - APIs REST com ASP.NET Web API
Curso de C# (C Sharp) - APIs REST com ASP.NET Web API
CONHEÇA O CURSO

Performance de uma pesquisa com 500 iterações – POCO serialization

MethodDurationRemarks
Hand coded (using a SqlDataReader)47ms
Dapper ExecuteMapperQuery49ms
ServiceStack.OrmLite (QueryById)50ms
PetaPoco52msCan be faster
BLToolkit80ms
SubSonic CodingHorror107ms
NHibernate SQL104ms
Linq 2 SQL ExecuteQuery181ms
Entity framework ExecuteStoreQuery631ms

Performance de uma pesquisa com 500 iterações – dynamic serialization

MethodDurationRemarks
Dapper ExecuteMapperQuery (dynamic)48ms
Massive52ms
Simple.Data95ms

Performance de uma pesquisa com 500 iterações – typical usage

MethodDurationRemarks
Linq 2 SQL CompiledQuery81msNot super typical involves complex code
NHibernate HQL118ms
Linq 2 SQL559ms
Entity framework859ms
SubSonic ActiveRecord.SingleOrDefault3619ms

Para exemplificar o seu uso, vamos ver um exemplo dele em uma aplicação ASP.NET Core.

Criando a aplicação

No terminal digite o código abaixo para criar uma aplicação chamada AspNetCoreDapper:

dotnet new mvc -n AspNetCoreDapper

Agora, adicione o pacote do Dappper:

dotnet add package Dapper

Não se esqueça de aplicar o restore no projeto:

dotnet restore

Com isso já podemos começar a nossa configuração Dapper.

Criando o model / entidade

Para este exemplo será utilizada a entidade abaixo:

using System;
using System.ComponentModel.DataAnnotations;

namespace AspNetCoreDapper.Models
{
    public class Product
    {
        public int Id { get; set; }

        [Required]
        public string Name { get; set; }
        public int Quantity { get; set; }

        [Required]
        public double Price { get; set; }
    }
}

Configurando o acesso ao banco com Dapper

Para este exemplo vou utilizar como banco de dados o SQLite, assim, antes de configurar o Dapper é necessário adicionar o pacote deste banco de dados na aplicação:

dotnet add package Microsoft.Data.Sqlite

Aplique o restore para iniciarmos o processo de configuração do banco.

Diferente de outros frameworks ORM, o Dapper não define uma classe de configuração. Assim, configuraremos o acesso dele na classe base dos repositórios:

namespace AspNetCoreDapper.Repositories
{
    public abstract class AbstractRepository<T>
    {
        private string _connectionString;
        protected string ConnectionString => _connectionString;
        public AbstractRepository(IConfiguration configuration){
            _connectionString = configuration.GetValue<string>("DBInfo:ConnectionString");

        }
        public abstract void Add(T item);
        public abstract void Remove(int id);
        public abstract void Update(T item);
        public abstract T FindByID(int id);
        public abstract IEnumerable<T> FindAll();
    }
} 

O Dapper também não cria o banco de dados de acordo com as entidades. Nele, caso o banco ainda não esteja criado, ele deve ser criado “na mão”. Que é o que iremos fazer na classe abaixo:

namespace AspNetCoreDapper.Db
{
    public class Seed
    {
        private static IDbConnection _dbConnection;

        private static void CreateDb(IConfiguration configuration)
        {
            var connectionString = configuration.GetValue<string>("DBInfo:ConnectionString");
            var dbFilePath = configuration.GetValue<string>("DBInfo:ConnectionString");
            if (!File.Exists(dbFilePath))
            {
                _dbConnection = new SqliteConnection(connectionString);
                _dbConnection.Open();

                // Create a Product table
                _dbConnection.Execute(@"
                    CREATE TABLE IF NOT EXISTS [Product] (
                        [Id] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
                        [Name] NVARCHAR(128) NOT NULL,
                        [Quantity] INTEGER NULL,
                        [Price] NUMERIC NOT NULL
                    )");

                _dbConnection.Close();
            }

        }
    }
}

E ele deve ser chamado na classe AbstractRepository:

public AbstractRepository(IConfiguration configuration){
    _connectionString = configuration.GetValue<string>("DBInfo:ConnectionString");

    AspNetCoreDapper.Db.Seed.CreateDb(configuration);
}

Para finalizar a configuração de acesso Dapper, iremos definir o repositório abaixo:

namespace AspNetCoreDapper.Repositories
{
    public class ProductRepository: AbstractRepository<Product>
    {
        public ProductRepository(IConfiguration configuration): base(configuration) { }

        public override void Add(Product item)
        {
            using (IDbConnection dbConnection = new SqliteConnection(ConnectionString))
            {
                string sQuery = "INSERT INTO Product (Name, Quantity, Price)"
                                + " VALUES(@Name, @Quantity, @Price)";
                dbConnection.Open();
                dbConnection.Execute(sQuery, item);
            }
        }
        public override void Remove(int id)
        {
            using (IDbConnection dbConnection = new SqliteConnection(ConnectionString))
            {
                string sQuery = "DELETE FROM Product" 
                            + " WHERE Id = @Id";
                dbConnection.Open();
                dbConnection.Execute(sQuery, new { Id = id });
            }
        }
        public override void Update(Product item)
        {
            using (IDbConnection dbConnection = new SqliteConnection(ConnectionString))
            {
                string sQuery = "UPDATE Product SET Name = @Name,"
                            + " Quantity = @Quantity, Price= @Price" 
                            + " WHERE Id = @Id";
                dbConnection.Open();
                dbConnection.Query(sQuery, item);
            }
        }
        public override Product FindByID(int id)
        { 
            using (IDbConnection dbConnection = new SqliteConnection(ConnectionString))
            {
                string sQuery = "SELECT * FROM Product" 
                            + " WHERE Id = @Id";
                dbConnection.Open();
                return dbConnection.Query<Product>(sQuery, new { Id = id }).FirstOrDefault();
            }
        }
        public override IEnumerable<Product> FindAll()
        { 
            using (IDbConnection dbConnection = new SqliteConnection(ConnectionString))
            {
                dbConnection.Open();
                return dbConnection.Query<Product>("SELECT * FROM Product");
            }
        }
    }
}

Note que o Dapper realiza o processo de impedância, a transformação dos dados do banco para entidades da aplicação de forma quase transparente:

dbConnection.Query<Product>("SELECT * FROM Product");

Na busca basta informar a classe que mapeia os dados do banco que o framework se encarregará de transformar os resultados obtidos em objetos desta classe. Aqui é importante que os nomes das colunas da tabela sejam iguais aos nomes das propriedades da classe, pois o Dapper irá realizar a transformação por Reflection.

Este mesmo processo é aplicado quando um dado é enviado para o banco:

using (IDbConnection dbConnection = new SqliteConnection(ConnectionString))
{
    string sQuery = "INSERT INTO Product (Name, Quantity, Price)"
                    + " VALUES(@Name, @Quantity, @Price)";
    dbConnection.Open();
    dbConnection.Execute(sQuery, item);
}

Também repare que é necessário informar o código SQL, pois este é um recurso dos frameworks ORM tradicionais que o Dapper não implementa. Assim, é possível definir um código mais eficiente, obtendo ainda mais performance.

Testando o Dapper

Para testar o Dapper, crie um controller:

namespace AspNetCoreDapper.Repositories
{
    public class ProductController : Controller
    {
        private readonly ProductRepository productRepository;

        public ProductController(IConfiguration configuration){
            productRepository = new ProductRepository(configuration);
        }

        // GET: Products
        public ActionResult Index()
        {
            return View(productRepository.FindAll().ToList());
        }

        // GET: Products/Details/5
        public ActionResult Details(int? id)
        {
            if (id == null)
            {
                return StatusCode(StatusCodes.Status404NotFound);
            }
            Product product = productRepository.FindByID(id.Value);
            if (product == null)
            {
                return StatusCode(StatusCodes.Status404NotFound);
            }
            return View(product);
        }

        // GET: Products/Create
        public ActionResult Create()
        {
            return View();
        }

        // POST: Products/Create
        // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
        // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create([Bind("Id,Name,Quantity,Price")] Product product)
        {
            if (ModelState.IsValid)
            {
                productRepository.Add(product);
                return RedirectToAction("Index");
            }

            return View(product);
        }

        // GET: Products/Edit/5
        public ActionResult Edit(int? id)
        {
            if (id == null)
            {
                return StatusCode(StatusCodes.Status400BadRequest);
            }
            Product product = productRepository.FindByID(id.Value);
            if (product == null)
            {
                return StatusCode(StatusCodes.Status404NotFound);
            }
            return View(product);
        }

        // POST: Products/Edit/5
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Edit([Bind("Id,Name,Quantity,Price")] Product product)
        {
            if (ModelState.IsValid)
            {
                productRepository.Update(product);
                return RedirectToAction("Index");
            }
            return View(product);
        }

        // GET: Products/Delete/5
        public ActionResult Delete(int? id)
        {
            if (id == null)
            {
                return StatusCode(StatusCodes.Status400BadRequest);
            }
            Product product = productRepository.FindByID(id.Value);
            if (product == null)
            {
                return StatusCode(StatusCodes.Status404NotFound);
            }
            return View(product);
        }

        // POST: Products/Delete/5
        [HttpPost, ActionName("Delete")]
        [ValidateAntiForgeryToken]
        public ActionResult DeleteConfirmed(int id)
        {
            productRepository.Remove(id);
            return RedirectToAction("Index");
        }
    }
}

Também crie as views as actions acima e então podemos ver o sistema funcionando:

Você pode fazer download dos sources dessa aplicação clicando aqui.

Até a próxima!

C# (C Sharp) - TDD
Curso de C# (C Sharp) - TDD
CONHEÇA O CURSO

Os caminhos das certificações Microsoft (parte 1)

Olá pessoal, tudo certinho?

Muitos alunos entram em contato conosco com dúvidas relacionadas à certificação. As dúvidas mais comuns são com relação a qual prova realizar, como é a prova e também quais as possíveis vantagens que um profissional certificado pode obter.

Neste primeiro de uma série de posts sobre certificações que faremos, vamos abordar estes aspectos sobre um dos grandes players neste segmento: a Microsoft.

C# (C Sharp) - Introdução ao ASP.NET Core
Curso de C# (C Sharp) - Introdução ao ASP.NET Core
CONHEÇA O CURSO

Áreas de conhecimento

A Microsoft divide as suas certificações em grandes áreas de conhecimento. Atualmente, as áreas existentes são:

  • Servers: área de conhecimento focada na parte de infraestrutura da Microsoft, contemplando Windows Server, SharePoint e até mesmo Azure;
  • Desktop: área de conhecimento focada em dispositivos Windows e mobilidade;
  • Applications: área de conhecimento focada em aplicações como Office, Office 365 e Dynamics CRM;
  • Database: área de conhecimento focada em bancos de dados e Business Intelligence com o SQL Server;
  • Developer: área de conhecimento voltada para desenvolvimento, abordando o .NET, Visual Studio e ferramentas de CI (Continuous Integration).

Neste post, levaremos mais em consideração a área Developer.

MCP? MTA? O que são essas siglas?

Antes de qualquer coisa, precisamos entender melhor algumas siglas que são famosas para certificados Microsoft. Nós temos duas siglas basicamente: MTA e MCP. É importante entendermos o significado de cada uma destas siglas para que possamos escolher até mesmo as provas que iremos prestar.

MTA é uma sigla para Microsoft Technology Associate. Este é um nível mais “iniciante” dentro da hierarquia das certificações Microsoft, sendo recomendado mais para estudantes e pessoas que estão em sua primeira experiência como profissionais de TI.

MCP é uma sigla para Microsoft Certified Professional. Essa já é uma certificação voltada para profissionais com experiência na respectiva área de conhecimento, além de ser mais reconhecida pelo mercado de trabalho.

Nada impede um estudante de uma faculdade fazer as provas voltadas ao programa dos MCPs, assim como nada impede um profissional que já tenha experiência de participar das provas relativas ao programa dos MTAs. Porém, são situações que não são muito comuns e devem ser evitadas.

Se um estudante de tecnologia fizer uma prova voltada para MCPs, ele poderá encontrar mais dificuldades que o esperado por ainda não ter uma bagagem de experiência que lhe dê mais suporte para a execução da prova. Da mesma maneira que, se um profissional que já tenha experiência fizer uma prova do programa dos MTAs, isso talvez não agregue tanto ao seu currículo. Portanto, aqui cabe uma auto-avaliação: se você julga que já tem uma experiência de mercado legal, caia de cara no programa dos MCPs.

Caso você não tenha tanta experiência ou ainda esteja em seu curso de graduação, considere fortemente realizar primeiramente as provas do programa para MTAs.

Certificações da área de conhecimento Developer

Vamos começar falando das certificações da área Developer, voltada para desenvolvedores de software.

Programa MTA (Microsoft Technology Associate)

No caso do programa para MTAs, surge a possibilidade de se realizar 3 provas de certificação:

  • Software Development Fundamentals (98-361): é a prova ideal para quem trabalha ou quer trabalhar com desenvolvimento de software, além de desejar seguir o programa MTA. Esta prova aborda questões básicas de desenvolvimento de software, como estruturas de decisão e de repetição e utilização de variáveis. Também são abordados o paradigma orientado a objetos, conceitos básicos de HTML, CSS e JavaScript e ainda conceitos de bancos de dados. Como pode ver, é de fato uma prova bem abrangente;

  • HTML 5 App Development Fundamentals (98-375): é uma prova específica para as principais tecnologias web (HTML 5, CSS 3 e JavaScript). Ela aborda conceitos básicos como utilização dos recursos do HTML 5 (canvas, tags específicas, semântica), utilização dos recursos do CSS 3 (CSSON, conceitos de layout, transições e transformações) e utilização de recursos do JavaScript (DOM, manipulação do DOM, jQuery, geolocalização, web sockets e até mesmo armazenamento com cache e local storage);

  • Software Testing Fundamentals (98-379): é uma prova específica para a área de teste de software. Essa prova está atualmente indisponível.

Veja que as provas do programa MTA, mesmo sendo em tese introdutórias, são muito abrangentes e extensas. Portanto, não se deixe enganar se for fazer uma prova do programa MTA! 😉

Para você ter o título de MTA, você não precisa realizar necessariamente as duas provas. A partir do momento que você conclui qualquer uma delas, você já passa a ser considerado um profissional MTA pela Microsoft. Porém, você ganha o título de MTA atrelado ao conteúdo da prova. Por exemplo, se você fizer o exame 98-361, você vira um profissional certificado Microsoft MTA: Software Development Fundamentals.

Caminhos - Microsoft MTA

Programa MCP (Microsoft Certified Professional)

O programa MCP já é um pouquinho mais complexo. Ele é dividido em duas vertentes: MCSA (Microsoft Certified Solutions Associate) e MCSD (Microsoft Certified Solutions Developer), sendo que a certificação MCSA está um nível abaixo da MCSD.

Para ambas as vertentes, você precisará completar um grupo de provas pré-determinado para que consiga obter o título correspondente. Agora, um ponto interessante: apesar de existirem os grupos de provas para que se consiga obter um determinado título, você não precisa realizar as provas em uma ordem pré-determinada. Para a Microsoft, a ordem em que você realizará as provas não é um fator importante, embora alguns grupos de provas sugiram uma sequência mais lógica.

Existe um outro ponto importante: a partir do momento em que você é aprovado na primeira prova do programa MCP, você já passa a ser reconhecido como um profissional MCP pela Microsoft. Agora, se quiser atingir os níveis acima do MCP, você precisará cumprir as provas previstas nos programas MCSA e MCSD.

O nível MCSA para desenvolvedores ainda pode se dividir em duas sub-trilhas: Web Applications e Universal Windows Platform. A primeira sub-trilha é focada em tecnologias Web, principalmente HTML5, CSS3, JavaScript e ASP.NET. Já a segunda sub-trilha é focada na plataforma universal do Windows e no desenvolvimento de aplicativos desktop com HTML5, CSS3 e JavaScript.

No caso da sub-trilha MCSA Web Applications, você precisará cumprir duas provas:

  • Programming in HTML5 with JavaScript and CSS3 (70-480): prova focada exclusivamente na base para qualquer aplicação web moderna: o HTML 5, o CSS 3 e o JavaScript. Os assuntos abordados na prova são muito similares à prova 98-375, porém, de uma maneira muito mais aprofundada tecnicamente falando;

  • Developing ASP.NET MVC Web Applications (70-486): é a prova da Microsoft para abordar o ASP.NET MVC 5. Apesar de o nome não deixar claro, uma dica: cai muita coisa sobre o Azure nesta prova também.

No caso da sub-trilha MCSA Universal Windows Platform, você também precisa cumprir duas provas:

  • Programming in C# (70-483): prova que aborda aspectos técnicos do C#. Nesta prova já caem conteúdos um pouco mais enroscados, como threading, tasks, reflection, aspectos avançados de orientação a objetos no C#, o namespace System.IO e até mesmo o LINQ;

  • Developing Mobile Apps (70-357): é uma prova recém-lançada pela Microsoft. Apesar do nome, ela na verdade foca na nova plataforma de desenvolvimento universal da Microsoft (Universal Windows Platform – UWP), o que engloba o próprio Windows, Windows Phone, Xbox e Surface.

Após você concluir alguma das subtrilhas MCSA, você estará apto a alcançar o título de MCSD.

Com relação ao MCSD, novamente nós temos mais seis sub-trilhas:

  • MCSD App Builder: é um título dado pela Microsoft quando esta reconhece que o profissional é capaz de criar aplicações web e/ou mobiles utilizando tecnologias modernas. Para atingir este nível, você precisará fazer ao menos uma das provas listadas abaixo. Ainda há um ponto importante a se ressaltar: você só pode obter o título de MCSD App Builder após obter o título de MCSA.
  1. Developing Microsoft Azure Solutions (70-532);
  2. Developing Microsoft Azure and Web Services (70-487);
  3. Developing Microsoft SharePoint Server 2013 Core Solutions (70-488);
  4. Developing Microsoft SharePoint Server 2013 Advanced Solutions (70-489);
  5. Universal Windows Platform – App Architecture and UX/UI (70-354);
  6. Universal Windows Platform – App Data, Services, and Coding Patterns (70-355);
  7. Administering Microsoft Visual Studio Team Foundation Server (70-496);
  8. Software Testing with Visual Studio (70-497);
  9. Delivering Continuous Value with Visual Studio Application Lifecycle Management (70-498).
  • MCSD Web Applications: o desenvolvedor que atinge esta certificação tem atestado pela Microsoft de que é capaz de desenvolver aplicações web com tecnologias modernas baseando-se no ASP.NET e no Azure. Esta é, naturalmente, a extensão da certificação MCSA Web Applications. Considerando que você já tenha o nível MCSA Web Applications, a única prova que você precisará prestar neste caso é a Developing Microsoft Azure and Web Services (70-487). Agora, um ponto importante: você não precisa necessariamente ter o nível MCSA para atingir este nível MCSD, como ocorre com a certificação MCSD App Builder. Isso quer dizer que você, além da prova 70-487, pode realizar também as provas Programming in HTML 5 with JavaScript and CSS 3 (70-480) e Developing ASP.NET MVC Web Applications (70-486). Se você passar por todas elas, você já é considerado um MCSD Web Applications e também um MCSA Web Applications, já que o MCSA exige as provas 70-480 e 70-486; provas também exigidas pelo MCSD com o acréscimo da prova 70-487. Existe mais um ponto legal para notarmos: quando você consegue o título de MCSD Web Applications, você também se torna automaticamente um MCSD App Builder. Isso ocorre porque, para você se tornar um MCSD Web Applications, você precisa fazer a prova 70-487, prova esta que conta também na lista de provas necessárias para a certificação MCSD App Builder;

  • MCSD SharePoint Applications: quando o desenvolvedor atinge esta certificação, a Microsoft atesta que o desenvolvedor é capaz de desenvolver aplicações e customizações dentro do SharePoint. Para você atingir esta certificação, você precisará cumprir as seguintes provas:

  1. Programming in HTML5 with JavaScript and CSS3 (70-480);
  2. Developing ASP.NET MVC Web Applications (70-486);
  3. Developing Microsoft SharePoint Server 2013 Core Solutions (70-488);
  4. Developing Microsoft SharePoint Server 2013 Advanced Solutions (70-489).
  • MCSD Application Lifecycle Management: esta linha de certificações é voltada para profissionais que lidam com o gerenciamento do ciclo de vida de aplicações. Esta trilha de certificações contempla as seguintes provas:
  1. Administering Microsoft Visual Studio Team Foundation Server (70-496);
  2. Software Testing with Visual Studio (70-497);
  3. Delivering Continuous Value with Visual Studio Application Lifecycle Management (70-498).
  • MCSD Azure Solutions Architect: linha de provas destinada a profissionais que utilizam a plataforma de nuvem Azure. Ela contempla as seguintes provas:
  1. Developing Microsoft Azure Solutions (70-532);
  2. Implementing Microsoft Azure Infrastructure Solutions (70-533);
  3. Architecting Microsoft Azure Solutions (70-534);
  • MCSD Universal Windows Platform: Este conjunto de provas visa atestar as habilidades do desenvolvedor com relação aos Windows Universal Apps. Esta trilha é composta pelas seguintes provas:
  1. Programming in C# (70-483);
  2. Universal Windows Platform – App Architecture and UX/UI (70-354);
  3. Universal Windows Platform – App Data, Services, and Coding Patterns (70-355).

O programa para MCPs com suas trilhas pode ser representado pela ilustração abaixo:

Caminhos - Microsoft MCP

Como você pode perceber, as trilhas contidas dentro do programa do MCP são um pouco mais complicadas de serem compreendidas no começo, já que existem muitas sub-trilhas. Porém, o grande ponto que você deve levar em conta ao escolher seguir uma determinada trilha é: “em que área quero me certificar?”. Quando você tiver de maneira clara e fixa este ponto em sua mente, a escolha de qual trilha ficará muito mais fácil. Por exemplo: se você quer se especializar no Azure, faz muito mais sentido você conseguir o título de MCP começando pela prova 70-532 e logo depois fazer as provas 70-533 e 70-534.

Por hora, é isso, rs. Temos muita coisa ainda para falar sobre as trilhas de certificações Microsoft. Nos próximos posts desta série, falaremos sobre a trilha da área de conhecimento Database. Também falaremos sobre preços para realização das provas, procedimentos de marcação para a realização das provas e também sobre como funcionam as provas de certificação da Microsoft. Se você tiver quaisquer dúvidas, não deixe de comentar aqui em baixo deste artigo! 😉

Até a próxima! o/

C# (C Sharp) - Introdução ao ASP.NET Core
Curso de C# (C Sharp) - Introdução ao ASP.NET Core
CONHEÇA O CURSO

Operações CRUD no ASP.NET MVC 5 com o ADO.NET

Conversando com um amigo ele contou como funciona a estrutura da aplicação ASP.NET MVC da empresa onde ele recentemente começou a trabalhar.

A minha surpresa foi saber que durante a migração da aplicação desktop para ASP.NET MVC eles optaram por continuar utilizando a mesma estrutura de acesso à base de dados com o clássico e bom ADO.NET.

Não pretendo discorrer nesse artigo se essa foi uma boa ou má escolha ou se seria melhor modificar todo o acesso à base de dados durante a migração e optar por outro framework ORM.

O que essa situação me mostrou é que mesmo com
várias opções, ainda há quem prefira utilizar o ADO.NET e não há muitos artigos abordando o uso deste framework com o ASP.NET MVC.

Sendo assim, mostrarei aqui como isso pode ser feito. Então, mãos à massa!

C# (C Sharp) - ASP.NET MVC
Curso de C# (C Sharp) - ASP.NET MVC
CONHEÇA O CURSO

Criando uma aplicação ASP.NET MVC

Para esse artigo estou utilizando o Visual Studio Community 2015. Nele, siga os procedimentos abaixo para criar o projeto.

  1. Com o Visual Studio aberto, em “File”, clique em “New > Project”, selecione o template “ASP.NET Web Application”, por fim, clique em OK.

A tela abaixo será apresentada:

  1. Como mostra a imagem, selecione a opção de template Empty e adicione a referência ao MVC. Depois clique em OK para criar o projeto:

Pronto! O nosso projeto está criado.

Criando classes do domínio

Aqui não vou me ater muito a padrões de projetos, mas para ficar próximo a uma estrutura comum quando se faz uso de um framework ORM, vamos criar no projeto classes de domínio e um repositório.

A classe de domínio será idêntica com uma classe de domínio de um framework ORM:

public class Pessoa
{
    public int Id { get; set; }

    [Required(ErrorMessage = "O campo nome é obrigatório.")]
    public string Nome { get; set; }

    public string Email { get; set; }

    public string Cidade { get; set; }

    public string Endereco { get; set; }
}

A anotação Required definida acima será utilizada para a validação na View.

Como a aplicação está utilizando o ADO.NET ela não poderá fazer uso das migrations do Entity (ou qualquer outro recurso equivalente em outro framework ORM), então, as classes de domínio devem representar exatamente a estrutura das tabelas do banco.

Ou seja, quando se utiliza o ADO.NET, deve ser empregado o princípio Database-First.

Criando o repositório

A criação do repositório é a parte parte mais crítica de uma aplicação que faz uso do ADO.NET, pois é nesta fase que as configurações de acesso são definidas.

Recomenda-se definir uma uma interface, mas neste exemplo, utilizarei uma classe abstrata:

public abstract class AbstractRepository<TEntity, TKey>
    where TEntity : class
{
    protected string StringConnection { get; } = WebConfigurationManager.ConnectionStrings["DatabaseCrud"].ConnectionString;

    public abstract List<TEntity> GetAll();
    public abstract TEntity GetById(TKey id);
    public abstract void Save(TEntity entity);
    public abstract void Update(TEntity entity);
    public abstract void Delete(TEntity entity);
    public abstract void DeleteById(TKey id);
}

Nesta classe, além dos métodos do repositório, é definido um atributo readonly (somente leitura), que obterá do arquivo web.config a string de conexão com o banco.

Nele é necessário definir essa string:

<connectionStrings>
  <add name="DatabaseCrud" connectionString="Data Source=(localdb)MSSQLLocalDB; Initial Catalog=DatabaseCrud-20161026144350; Integrated Security=True; MultipleActiveResultSets=True;"
    providerName="System.Data.SqlClient" />
</connectionStrings>

Agora implementaremos o repositório da nossa entidade:

public class PessoaRepository : AbstractRepository<Pessoa, int>
{
    ///<summary>Exclui uma pessoa pela entidade
    ///<param name="entity">Referência de Pessoa que será excluída.</param>
    ///</summary>
    public override void Delete(Pessoa entity)
    {
        using (var conn = new SqlConnection(StringConnection))
        {
            string sql = "DELETE Pessoa Where Id=@Id";
            SqlCommand cmd = new SqlCommand(sql, conn);
            cmd.Parameters.AddWithValue("@Id", entity.Id);
            try
            {
                conn.Open();
                cmd.ExecuteNonQuery();
            }
            catch (Exception e)
            {
                throw e;
            }
        }
    }

    ///<summary>Exclui uma pessoa pelo ID
    ///<param name="id">Id do registro que será excluído.</param>
    ///</summary>
    public override void DeleteById(int id)
    {
        using (var conn = new SqlConnection(StringConnection))
        {
            string sql = "DELETE Pessoa Where Id=@Id";
            SqlCommand cmd = new SqlCommand(sql, conn);
            cmd.Parameters.AddWithValue("@Id", id);
            try
            {
                conn.Open();
                cmd.ExecuteNonQuery();
            }
            catch (Exception e)
            {
                throw e;
            }
        }
    }

    ///<summary>Obtém todas as pessoas
    ///<returns>Retorna as pessoas cadastradas.</returns>
    ///</summary>
    public override List<Pessoa> GetAll()
    {
        string sql = "Select Id, Nome, Email, Cidade, Endereco FROM Pessoa ORDER BY Nome";
        using (var conn = new SqlConnection(StringConnection))
        {
            var cmd = new SqlCommand(sql, conn);
            List<Pessoa> list = new List<Pessoa>();
            Pessoa p = null;
            try
            {
                conn.Open();
                using (var reader = cmd.ExecuteReader(CommandBehavior.CloseConnection))
                {
                    while (reader.Read())
                    {
                        p = new Pessoa();
                        p.Id = (int)reader["Id"];
                        p.Nome = reader["Nome"].ToString();
                        p.Email = reader["Email"].ToString();
                        p.Cidade = reader["Cidade"].ToString();
                        p.Endereco = reader["Endereco"].ToString();
                        list.Add(p);
                    }
                }
            }
            catch(Exception e)
            {
                throw e;
            }
            return list;
        }
    }

    ///<summary>Obtém uma pessoa pelo ID
    ///<param name="id">Id do registro que obtido.</param>
    ///<returns>Retorna uma referência de Pessoa do registro encontrado ou null se ele não for encontrado.</returns>
    ///</summary>
    public override Pessoa GetById(int id)
    {
        using (var conn = new SqlConnection(StringConnection))
        {
            string sql = "Select Id, Nome, Email, Cidade, Endereco FROM Pessoa WHERE Id=@Id";
            SqlCommand cmd = new SqlCommand(sql, conn);
            cmd.Parameters.AddWithValue("@Id", id);
            Pessoa p = null;
            try
            {
                conn.Open();
                using (var reader = cmd.ExecuteReader(CommandBehavior.CloseConnection))
                {
                    if (reader.HasRows)
                    {
                        if (reader.Read())
                        {
                            p = new Pessoa();
                            p.Id = (int)reader["Id"];
                            p.Nome = reader["Nome"].ToString();
                            p.Email = reader["Email"].ToString();
                            p.Cidade = reader["Cidade"].ToString();
                            p.Endereco = reader["Endereco"].ToString();
                        }
                    }
                }
            }
            catch (Exception e)
            {
                throw e;
            }
            return p;
        }
    }

    ///<summary>Salva a pessoa no banco
    ///<param name="entity">Referência de Pessoa que será salva.</param>
    ///</summary>
    public override void Save(Pessoa entity)
    {
        using (var conn = new SqlConnection(StringConnection))
        {
            string sql = "INSERT INTO Pessoa (Nome, Email, Cidade, Endereco) VALUES (@Nome, @Email, @Cidade, @Endereco)";
            SqlCommand cmd = new SqlCommand(sql, conn);
            cmd.Parameters.AddWithValue("@Nome", entity.Nome);
            cmd.Parameters.AddWithValue("@Email", entity.Email);
            cmd.Parameters.AddWithValue("@Cidade", entity.Cidade);
            cmd.Parameters.AddWithValue("@Endereco", entity.Endereco);
            try
            {
                conn.Open();
                cmd.ExecuteNonQuery();
            }
            catch (Exception e)
            {
                throw e;
            }
        }
    }

    ///<summary>Atualiza a pessoa no banco
    ///<param name="entity">Referência de Pessoa que será atualizada.</param>
    ///</summary>
    public override void Update(Pessoa entity)
    {
        using (var conn = new SqlConnection(StringConnection))
        {
            string sql = "UPDATE Pessoa SET Nome=@Nome, Email=@Email, Cidade=@Cidade, Endereco=@Endereco Where Id=@Id";
            SqlCommand cmd = new SqlCommand(sql, conn);
            cmd.Parameters.AddWithValue("@Id", entity.Id);
            cmd.Parameters.AddWithValue("@Nome", entity.Nome);
            cmd.Parameters.AddWithValue("@Email", entity.Email);
            cmd.Parameters.AddWithValue("@Cidade", entity.Cidade);
            cmd.Parameters.AddWithValue("@Endereco", entity.Endereco);
            try
            {
                conn.Open();
                cmd.ExecuteNonQuery();
            }
            catch (Exception e)
            {
                throw e;
            }
        }
    }
}

Note que em todos os métodos a conexão é aberta manualmente. Ela será fechada graças ao uso do using. E todos os métodos “lançam” as exceções geradas para o respectivo método do repositório que fora invocado.

C# (C Sharp) - ASP.NET MVC
Curso de C# (C Sharp) - ASP.NET MVC
CONHEÇA O CURSO

Criando o Controller

Caso estivéssemos utilizando o Entity, o controller poderia ser criado já com as views, mas no nosso ambiente, teremos que criá-los separadamente. O controller será criado com as actions:

Com o uso do repositório terá o código abaixo:

public class PessoaController : Controller
{
    private PessoaRepository respository = new PessoaRepository();
    // GET: Pessoa
    public ActionResult Index()
    {
        return View(respository.GetAll());
    }

    // GET: Pessoa/Create
    public ActionResult Create()
    {
        return View();
    }

    // POST: Pessoa/Create
    [HttpPost]
    public ActionResult Create(Pessoa pessoa)
    {
        if (ModelState.IsValid)
        {
            respository.Save(pessoa);
            return RedirectToAction("Index");
        }
        else { 
            return View(pessoa);
        }
    }

    // GET: Pessoa/Edit/5
    public ActionResult Edit(int id)
    {
        var pessoa = respository.GetById(id);

        if (pessoa == null)
        {
            return HttpNotFound();
        }

        return View(pessoa);
    }

    // POST: Pessoa/Edit/5
    [HttpPost]
    public ActionResult Edit(Pessoa pessoa)
    {
        if (ModelState.IsValid)
        {
            respository.Update(pessoa);
            return RedirectToAction("Index");
        }
        else
        {
            return View(pessoa);
        }
    }

    // POST: Pessoa/Delete/5
    [HttpPost]
    public ActionResult Delete(int id)
    {
        respository.DeleteById(id);
        return Json(respository.GetAll());      
    }
}

Pronto, agora só é necessário definir as Views.

Criando as views

O código das views é bem simples, pois podemos utilizar o scaffolding do Visual Studio.

No controller clique com o botão direito sobre a
action Index, e selecione Add View. A tela abaixo será apresentada:

Configure os dados conforme a imagem acima. Agora, modifique o código gerado para este:

@model IEnumerable<CRUDUsingMVCwithAdoNet.Models.Pessoa>
@{
    ViewBag.Title = "Index";
}
<h2>Index</h2>

<p>
    @Html.ActionLink("Adicionar Pessoa", "Create")
</p>
<table class="table" id="tblPessoas">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Nome)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Email)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Cidade)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model)
        {
            <tr>
                <td>
                    @Html.DisplayFor(modelItem => item.Nome)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Email)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Cidade)
                </td>
                <td>
                    @Html.ActionLink("Editar", "Edit", new { id = item.Id }) |
                    <button type="button" class="btn btn-link" data-item="@item.Id">Deletar</button>
                </td>
            </tr>
        }
    </tbody>
</table>
@section Scripts {
<script type="text/javascript">
        $(document).ready(function () {
            $(".btn-link").click(function () {
                var id = $(this).attr('data-item');
                if (confirm("Você tem certeza que gostaria de excluir este registro?")) {
                    $.ajax({
                        method: "POST",
                        url: "/Pessoa/Delete/" + id,
                        success: function (data) {
                            $("#tblPessoas tbody > tr").remove();
                            $.each(data, function (i, pessoa) {
                                $("#tblPessoas tbody").append(
                                    "<tr>" +
                                    "   <td>" + pessoa.Nome + "</td>" +
                                    "   <td>" + pessoa.Email + "</td>" +
                                    "   <td>" + pessoa.Cidade + "</td>" +
                                    "   <td>" +
                                    "       <a href='/Pessoa/Edit/" + pessoa.Id + "'>Editar</a> |" +
                                    "       <button type="button" class="btn btn-link" data-item="" + pessoa.Id + "">Deletar</button>" +
                                    "   </td>" +
                                    "</tr>"
                                );
                            });
                        },
                        error: function (data) {
                            alert("Houve um erro na pesquisa.");
                        }
                    });
                }
            });
        });
</script>
}

Como mostra o código acima, para não ser necessário criar uma view de exclusão, esta funcionalidade já é definida na listagem.

Repita o mesmo procedimento para termos a view Create:

@model CRUDUsingMVCwithAdoNet.Models.Pessoa
@{
    ViewBag.Title = "Cadastrar";
}

<h2>Cadastrar nova Pessoa</h2>


@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()

    <div class="form-horizontal">
        <h4>Pessoa</h4>
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        <div class="form-group">
            @Html.LabelFor(model => model.Nome, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Nome, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Nome, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Email, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Email, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Email, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Cidade, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Cidade, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Cidade, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Endereco, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Endereco, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Endereco, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Cadastrar" class="btn btn-default" />
            </div>
        </div>
    </div>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>
<script src ="~/Scripts/jquery.validate.min.js" />
<script src ="~/Scripts/jquery.validate.unobtrusive.min.js" />

E agora a view Edit:

@model CRUDUsingMVCwithAdoNet.Models.Pessoa
@{
    ViewBag.Title = "Editar";
}

<h2>Editar</h2>


@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()

    <div class="form-horizontal">
        <h4>Pessoa</h4>
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        @Html.HiddenFor(model => model.Id)

        <div class="form-group">
            @Html.LabelFor(model => model.Nome, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Nome, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Nome, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Email, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Email, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Email, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Cidade, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Cidade, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Cidade, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Endereco, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Endereco, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Endereco, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Salvar" class="btn btn-default" />
            </div>
        </div>
    </div>
}

<div>
    @Html.ActionLink("Voltar a listagem", "Index")
</div>
<script src="~/Scripts/jquery.validate.min.js" />
<script src="~/Scripts/jquery.validate.unobtrusive.min.js" />

Como criamos o projeto a partir de um template vazio, não se esqueça de adicionar os scripts do jQuery Validation (https://jqueryvalidation.org/) e do jQuery Validation Unobtrusive (https://github.com/aspnet/jquery-validation-unobtrusive).

Pronto! A aplicação está pronta. Agora é só testar.

Executando a aplicação

Ao executar a aplicação a tela abaixo será mostrada:

Clique em Adicionar Pessoa para inserir um novo registro.

Se no cadastro nada for informado:

A validação funcionará. Se dados forem informados, o registro será salvo no banco e listado:

Caso clique em Editar, a edição estará funcionando perfeitamente:

E no caso da exclusão é exibida uma caixa de diálogo:

Que ao ser confirmada o registro será excluído:

Conclusão

Os frameworks ORM trazem produtividade para o desenvolvimento, mas o seu uso não pode nos cegar em relação ao legado. Caso seja necessário, é possível fazer uso do ADO.NET no ASP.NET MVC, sem estresse. Esta substituição só será sentida na produtividade devido a não possibilidade de uso de alguns recursos do Visual Studio.

C# (C Sharp) - ASP.NET MVC
Curso de C# (C Sharp) - ASP.NET MVC
CONHEÇA O CURSO