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!

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.

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.

Deixe seu comentário

Instrutor, nerd, cinéfilo e desenvolvedor nas horas vagas. Graduado em Ciências da Computação pela Universidade Metodista de São Paulo.

JUNTE-SE A MAIS DE 150.000 PROGRAMADORES