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!
Curso C# (C Sharp) - ASP.NET MVC
Conhecer o cursoCriando 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.
- 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:
- 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.
Curso C# (C Sharp) - ASP.NET MVC
Conhecer o cursoCriando 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.