Na arquitetura de uma aplicação ASP.NET tradicional, normalmente utilizamos um banco de dados relacional e criamos uma interface com controles data-bound. Com os novos controles de acesso a dados introduzidos pelo ASP.NET 2.0, podemos utilizar um SqlDataSource por exemplo para conectar a um banco de dados e manipular informações em tela.
Mas será que essa é a melhor forma de desenvolver aplicações ASP.NET? Será que é a maneira correta? Não é estranho ter funcionalidades de acesso a dados combinadas com controles de UI? E a POO, como tirar do papel e colocar em prática? É muito comum um desenvolvedor, pela pressão em finalizar logo um projeto ou mesmo por falta de conhecimento, sair colocando código SQL no próprio Web Form. E essa com certeza não é uma boa prática.
O objetivo deste artigo é ensinar você leitor a aplicar boas práticas de desenvolvimento de aplicações ASP.NET. Você aprenderá, através de um exemplo prático passo a passo, como construir uma solução Web utilizando boas práticas de desenvolvimento, inclusive algumas recomendadas pela própria Microsoft.
O que vamos desenvolver é uma aplicação distribuída em camadas, utilizando o melhor do que a POO (Programação Orientada a Objetos) tem a oferecer. Podemos dizer que nossa solução será praticamente 100% Orientada a Objetos.
A metodologia que utilizarei é simples, porém interessante. Iniciaremos criando uma aplicação da forma tradicional, com código de acesso a dados mesclado com o código de interface. A seguir, vamos começar a separar a aplicação em partes (camadas), incluindo código e funcionalidade em cada uma. O porquê de dividir em camadas é explicado a seguir.
Antes de iniciar, vou apresentar o nosso “mapa”. A Figura 1 representa a solução que vamos desenvolver. É importantíssimo que você entenda cada parte da aplicação multicamadas e as siglas utilizadas, pois elas serão referenciadas durante todo este tutorial.
Durante o desenvolvimento da solução, sempre que tiver uma dúvida em relação a qual camada estamos implementando, determinada funcionalidade, consulte essa figura.

Explicando as camadas:
- Banco de Dados (DB): será um banco no SQL Server 2005 Express;
- Data Access Layer (DAL): é a camada que cuida e centraliza o acesso a dados. Aqui ficam os códigos para inserir e atualizar dados nas tabelas do banco e onde são utilizadas as classes do ADO.NET, ou seja, é a camada encarregada da persistência dos dados;
- Business Logic Layer (BLL): camada de regra de negócio, funciona como "ponte" entre a DAL e DB. Contém regras de negócio da aplicação, validações a nível de objeto etc.
- Presentation ou Interface: a aplicação ASP.NET propriamente dita. Note que podemos utilizar outro tipo de interface, como Windows Forms ou um dispositivo móvel (com o auxílio de um Web Service).
Por que desenvolver dessa forma (vantagens)
Muitos podem estar se perguntando: Por que eu devo desenvolver dessa forma e não da maneira como estou acostumado? Quais as reais vantagens que eu ganho? Vamos enumerar os principais benefícios:
- A camada de apresentação contém apenas código de UI, ela não acessa o banco de dados diretamente, não tem conhecimento sobre tabelas, esquemas, qual o tipo do banco de dados, apenas cuida de apresentar os dados para o usuário e manipulá-los. Com isso, podemos dizer que a aplicação cliente é thin-client (cliente leve). Isso nos dá um nível elevado de abstração;
- Independência de tipo de aplicação cliente: como o acesso a dados é centralizado, podemos compartilhar esse código de acesso entre vários tipos de aplicações cliente, como Web, Desktop e Mobile;
- Migração facilitada: Como estamos separando a solução em camadas, uma possível migração fica muito simples, já que tudo está separado. Por exemplo, se quisermos migrar de SQL Server para Oracle, apenas uma camada (a DAL) precisaria sofrer alguns ajustes, as outras camadas não enxergam esse acesso;
- Reaproveitamento de código: o código de acesso a dados (e BLL), por exemplo, não é replicado caso tenhamos vários tipos de aplicativos cliente ou caso tenhamos múltiplas interfaces fazendo uso dos mesmos objetos;
- Fácil substituição:: para explicar a POO, muito recorre-se à vida real para fazer comparativos, vou fazer o mesmo aqui. Imagine um computador, ele é formado por vários objetos, como se fossem “camadas” que se comunicam. Se uma peça queimar ou você quiser fazer um upgrade, precisará trocar somente aquele objeto, e não o computador todo. O mesmo acontece em nossa solução. Se você desejar implementar um mecanismo mais robusto de acesso a dados, basta criar uma nova camada DAL e substituir na arquitetura, as demais partes permanecem inalteradas;
- Manutenção facilitada: text
- text: como os aplicativos estão em camadas, se houver um erro ou for necessário fazer uma manutenção / correção, normalmente apenas uma camada é afetada, o que também facilita o deploy;
- E finalmente, estaremos usando o melhor da POO. Eu fico imaginando como não era trabalhoso e árduo trabalhar com o ASP clássico sem esses recursos (o include era o melhor recurso para “herança”!). Em nossa solução vamos usar e desfrutar de recursos da POO pura, como classes, herança, encapsulamento e muito mais, além de um novo e poderoso recurso do ASP.NET 2.0: o ObjectDataSource.
Explicados os fundamentos, arquitetura e vantagens da solução proposta, vamos para a prática.
Criando o banco de dados
Nosso aplicativo vai gerenciar edições de uma revista, nesse caso a .net Magazine. Caso queira, você poderá utilizar um banco de dados pronto, como o Northwind.
Utilizando o SQL Server Management Studio Express (veja seção Links), crie um novo banco chamado “EDITORA”. Nele, crie uma nova tabela chamada “Revistas”, usando o esquema da Figura 2.

Nota: Para fins didáticos, simplificamos ao máximo a estrutura da tabela, não normalizando alguns campos que precisariam ser normalizados. Além disso, vamos trabalhar com uma única tabela em nosso banco de dados, facilitando o exemplo e para que possamos nos concentrar nos aspectos relativos à POO e multicamadas.
Após criar a tabela, insira alguns registros usando o próprio gerenciador (opção Open Table). Cadastre algumas revistas, no meu caso cadastrei as últimas edições da .net Magazine. O campo Capa indica qual foi a matéria de capa e o campo Nivel indica a média do nível da revista, entre avançado e iniciante, onde 3 indica iniciante, 2 intermediário e 1 avançado.
Esse número na prática é obtido através de uma média feita pela pontuação dada a cada artigo, mas como não estamos trabalhando com uma tabela de artigos, optamos por colocar diretamente na tabela de revistas. Obviamente os valores apresentados são fictícios e não representam os valores reais de cada edição, servem apenas para teste e ilustração.
Construindo a interface
No Visual Studio, crie uma nova solução em branco, através do menu File>New Project escolhendo a opção Other Project Types>Visual Studio Solutions>Blank Solution. Dê o nome de “Multitier” para a solução e salve-a na pasta C:\. Uma solução é basicamente um grupo de projetos relacionados e em nosso caso vai ajudar a gerenciar todas as camadas a partir do Solution Explorer.
Clique em File>New Web Site e escolha ASP.NET Ajax - Enabled Web Site ou simplesmente ASP.NET Web Site caso não queria usar o AJAX ou não o tenha instalado. Escolha a linguagem C#, File System e dê a localização do projeto para “C:\Multitier\InterfaceWeb”. Clique em OK.
Conforme comentei, vamos inicialmente construir uma aplicação padrão, semelhante às aplicações que encontramos, sem separação em camadas. Em um primeiro momento, vamos fazer tudo na interface cliente, depois vamos começar a dividir em camadas.
A interface é simples, temos um GridView e um DetailsView ligados a um SqlDataSource, que por sua vez está conectado ao banco Editora e recupera todos os registros da tabela Revistas. Para configurar o SqlDataSource, basta colocar o componente e escolher a opção Configure Data Source. Lembre-se de habilitar a inserção, atualização e exclusão em Advanced Options.
Ambos DetailsView e GridView estão dentro de um UpdatePanel do ASP.NET AJAX (coloque um a partir da ToolBox), o que permitirá operações assíncronas e sem postbacks ao servidor, como a edição e atualização de dados.
O DetailsView servirá apenas para a inserção, então defina sua propriedade DefaultMode para Insert e AutoGenerateInsertButton para True. O GridView fará a edição, então habilite a opção Enable Editing nas tasks do controle. Habilite também Enable Paging e Enable Sorting.
Executando a aplicação, sua tela deve estar semelhante à Figura 3. Inclua alguns registros, pagine o GridView, ordene, edite, e observe que tudo é feito sem postbacks, graças ao ASP.NET AJAX.

Data Acess Layer – DAL (Camada de Acesso a Dados)
Nosso primeiro passo vai ser retirar o código de acesso a dados da interface. Nossa camada de acesso a dados (DAL) será composta de apenas uma classe, chamada Revista, que basicamente contém os métodos para manipular os dados, conhecidos também como métodos CRUD (Create, Read, Update e Delete).
Você pode incluir métodos personalizados para, por exemplo, retornar todas as revistas que tiveram nível avançado. A nossa camada DAL será criada em uma Class Library a parte, para indicar a separação em camadas.
Clique então em File > Add > New Project > Visual C# > Windows > Class Library para criar uma nova biblioteca DLL (assembly). Dê o nome de “DAL” para a Class Library e Location “C:\Multitier”. No Solution Explorer modifique o nome do arquivo criado para “Revista.cs”. Codifique a nova classe conforme mostrado na Listagem 1.
...
using System.Data;
using System.Data.SqlClient;
using System.Configuration;
namespace DAL
{
public class Revista
{
private String ConStr = @"Data Source=ASUS\"+
"SQLEXPRESS;Initial Catalog=EDITORA;Integrated"+
" Security=True";
public DataSet Select()
{
SqlConnection Con = new SqlConnection(ConStr);
string SQL = "select * from REVISTAS";
SqlCommand Cmd = new SqlCommand(SQL, Con);
SqlDataAdapter da = new SqlDataAdapter(Cmd);
DataSet ds = new DataSet();
da.Fill(ds);
return ds;
}
public void Update(int NUM_EDICAO,
string CAPA,double NIVEL)
{
SqlConnection Con = new SqlConnection(ConStr);
string SQL = "update REVISTAS set " +
"CAPA=@CAPA,NIVEL=@NIVEL " +
"where NUM_EDICAO=@NUM_EDICAO_Original";
SqlCommand Cmd = new SqlCommand(SQL, Con);
Cmd.Parameters.AddWithValue("@CAPA",CAPA);
Cmd.Parameters.AddWithValue("@NIVEL",NIVEL);
Cmd.Parameters.AddWithValue(
"@NUM_EDICAO_Original",NUM_EDICAO);
Con.Open();
try {
Cmd.ExecuteNonQuery();
}
finally
{
Con.Close();
}
}
public void Insert(int NUM_EDICAO,
string CAPA,double NIVEL)
{
SqlConnection Con = new SqlConnection(ConStr);
string SQL = "insert into REVISTAS" +
"values(@CAPA,@NIVEL,@NUM_EDICAO)";
SqlCommand Cmd = new SqlCommand(SQL, Con);
Cmd.Parameters.AddWithValue("@CAPA",CAPA);
Cmd.Parameters.AddWithValue("@NIVEL",NIVEL);
Cmd.Parameters.AddWithValue("@NUM_EDICAO",
NUM_EDICAO);
Con.Open();
try
{
Cmd.ExecuteNonQuery();
}
finally
{
Con.Close();
}
}
public void Delete(int NUM_EDICAO)
{
SqlConnection Con = new SqlConnection(ConStr);
string SQL = "delete REVISTAS" +
"where NUM_EDICAO=@NUM_EDICAO)";
SqlCommand Cmd = new SqlCommand(SQL, Con);
Cmd.Parameters.AddWithValue("@NUM_EDICAO",
NUM_EDICAO);
Con.Open();
try
{
Cmd.ExecuteNonQuery();
}
finally
{
Con.Close();
}
}
}
}
Aqui estamos basicamente utilizando classes do ADO.NET para comunicação com o banco de dados. O Select utiliza um SqlDataAdapter parar preencher um DataSet e devolver para a camada que chamou a DAL. O Update, Insert e Delete utilizam SqlCommands parar executar comandos DML no banco. Note que eles recebem os valores dos parâmetros das instruções SQL diretamente nos parâmetros da assinatura do método, como, por exemplo, NUM_EDICAO, CAPA e NIVEL.
Lembre-se de adaptar a string de conexão para refletir sua configuração. Uma opção seria armazenar essa string em um arquivo de configuração.
Agora precisamos fazer com que a camada de interface (Web Site) reconheça a DAL. Dê um Build no projeto Class Library e vá até a aplicação de interface. No Solution Explorer, dê um clique de direita sobre o nome do Web Site e escolha a opção Add Reference. Não é necessário buscar a Class Library no disco porque ela está na mesma solução, então na aba Projects basta escolher DAL e clicar em OK.
Na interface principal do Web Site, coloque um ObjectDataSource (ODS) e retire o SqlDataSource (o componente deve ficar dentro do UpdatePanel). Configure o DataSourceID dos controles de tela para apontarem para o ODS. Nas tasks do novo controle ative a opção Configure Data Source para abrir um assistente de configuração.
O ObjectDataSource também é um tipo IDataSource, assim como o SqlDataSource. É utilizado para criar aplicativos em camadas, já que pode reconhecer um objeto de negócio como fonte de dados e chamar métodos para obter e atualizar dados. Nesta primeira etapa, o objeto de negócio será a classe Revista da DAL. Diferente do SqlDataSource, que armazena instruções SQL junto com a interface, o ODS apenas referencia a classe de negócio.
Então, em Choose a Business Object escolha DAL.Revistas, na próxima tela em Select escolha o método de mesmo nome e repita o mesmo passo para os demais métodos. Clique a seguir em Finish. Execute a aplicação e faça testes de seleção, inserção, update etc.
Nesse momento, nossa aplicação já é multicamadas (e veja que a interface não muda aparentemente em nada). Temos o banco de dados, a camada DAL e a interface. Muitos desenvolvedores já poderiam parar por aqui e optar por essa arquitetura, que já é bastante flexível. Mais adiante vamos dar um passo além e criar mais uma camada na solução, a BLL.
A aplicação como está agora se assemelha muito a tradicional aplicação ASP.NET com DataSets tipados e TableAdapters. Essa é outra possível solução de arquitetura em camadas, e já foi amplamente discutida em edições anteriores. Em nosso aplicativo criamos nossa própria DAL para ter mais flexibilidade e também para adicionar um ingrediente extra na receita.
Note que nossa camada DAL devolve DataSets para a camada de interface, mas poderia trocar outros tipos de dados, como IDataReaders, documentos XML ou classes customizadas, minha opção preferida (veja mais adiante).
Nota: Uma outra boa prática seria usar Data Access Application Blocks nesse camada.
Stored Procedures
Já que estamos falando em boas práticas não poderíamos deixar de falar em Stored Procedures. Primeiro, o uso de SPs adiciona performance. Além disso, você mantém todas as instruções SQL no próprio BD e esconde da aplicação detalhes sobre estruturas de tabelas, criando mais um nível de isolamento em nossa solução.
Usando o Management Studio Express, abra o banco criado e no item Programmability dê um clique de direita sobre o item Stored Procedures. Usando a opção New Procedure, crie as procedures mostradas na Listagem 2. A seguir, clique no botão Execute.
-- SELECT
create procedure SelectRevistas
as
begin
select * from REVISTAS
end
GO
-- UPDATE
create procedure UpdateRevista
@NUM_EDICAO int,
@CAPA varchar(250),
@NIVEL decimal(18,2)
as
begin
update REVISTAS set
CAPA = @CAPA,
NIVEL = @NIVEL
where NUM_EDICAO = @NUM_EDICAO
end
GO
-- INSERT
create procedure InsertRevista
@NUM_EDICAO int,
@CAPA varchar(250),
@NIVEL decimal(18,2)
as
begin
insert into REVISTAS values
(@NUM_EDICAO,@CAPA,@NIVEL)
end
GO
-- DELETE
create procedure DeleteRevista
@NUM_EDICAO int
as
begin
delete REVISTAS
where NUM_EDICAO = @NUM_EDICAO
end
GO
Modifique a camada DAL para que faça uso das Stored Procedures, bastando para isso configurar os SqlCommands. Por exemplo, o método para seleção fica:
SqlCommand Cmd = new SqlCommand("SelectRevistas", Con);
Cmd.CommandType = CommandType.StoredProcedure;
Observe que no lugar do comando SQL agora passamos o nome da SP a ser utilizada. A seguir, configuramos o CommandType do SqlCommand para indicar que vamos executar uma SP. A variável SQL e sua string foram removidas pois não é mais necessário armazenar comandos SQL na DAL.
Faça o mesmo procedimento para os demais métodos e certifique-se que o nome de cada parâmetro que esteja passando na collection Parameters corresponda ao nome do parâmetro que está na SP.
Execute e teste a aplicação para uso com SPs. Veja que nada muda na interface, ela continua funcionando da mesma forma que no início do artigo. Os reais ganhos são internos à arquitetura, com performance, isolamento e abstração.
Criando Entidades de Negócio
A seguir, vamos criar mais uma camada em nossa solução, a BLL. Nesse caso, informações sobre revistas precisam ser trafegadas de uma camada para outra da aplicação. Em nosso exemplo, até agora, estamos usando DataSets para devolver dados para a interface e métodos com parâmetros usando tipos escalares (int, string, double) para atualizar dados.
O que vamos fazer agora é definir uma classe que representará a entidade Revista, ou seja, a representação OO de uma tabela do modelo relacional (processo conhecido como mapeamento O/R- objeto/relacional). Essa entidade será utilizada para comunicar as diferentes camadas da aplicação.
Aqui optamos por fazer um mapeamento 1-1 (uma tabela representa uma classe), mas nada impede de você manipular mais de uma tabela em uma única classe. Por exemplo, nossa classe Revista poderia ter métodos para manipular ambas as tabelas de Revistas e Artigos (caso houvesse). O modelo de classes não precisa ser um espelho do modelo relacional.
A camada de interface não vai mais receber um DataSet para exibir os dados, mas uma coleção de objetos do tipo Revista. Quando for atualizar os dados, não chamará um método passando os valores a serem atualizados, mas um objeto contendo em seus atributos os valores a serem persistidos no BD.
A Figura 4 representa a troca de objetos customizados entre as camadas da aplicação.

Para criar a nova classe, vamos definir uma nova Class Library, pois ela precisará ser acessada e compartilhada por todas as camadas. Clique em File>Add>New Project>Visual C#>Windows>Class Library para criar uma nova biblioteca DLL. Dê o nome de “Types” para a Class Library e Location “C:\Multitier”. No Solution Explorer, modifique o nome do arquivo criado para “Revista.cs”. Implemente-o conforme a Listagem 3.
namespace Types
{
public class Revistas : List
{
}
public class Revista
{
private int _NumEdicao;
private string _Capa;
private double _Nivel;
public int NumEdicao
{
get { return _NumEdicao; }
set { _NumEdicao = value; }
}
public string Capa
{
get { return _Capa; }
set { _Capa = value; }
}
public double Nivel
{
get { return _Nivel; }
set { _Nivel = value; }
}
}
}
A classe mapeia fielmente a tabela de revistas, tendo uma propriedade para cada campo da tabela. Para armazenar os valores, são utilizados campos (fields) privados que são internos à classe.
Note que logo acima da classe Revista temos a classe Revistas. Aqui usamos um recurso do C# 2.0, chamado Generics, para criar uma lista fortemente tipada de objetos Revista. Esse tipo será usado para representar uma coleção de objetos, normalmente retornada por um método Select.
Caso queira usar o designer de diagrama de classes do Visual Studio, basta acioná-lo a partir do Solution Explorer e desenhar as classes visualmente, conforme mostra a Figura 5. Você pode criar os fields e a seguir usar o Refactor Encapsulate Field para facilmente criar as propriedades associadas.

A função dessa camada é abstrair a camada DAL da camada de interface, funcionando como um “meio de campo”. Nessa camada você pode implementar regras de negócio específicas para tratar determinados dados, como validações, realizar cálculos e manipulação de dados antes de serem enviados a interface ou serem atualizados pela DAL (como definição de valores default). Pode também filtrar os dados a serem exibidos de acordo com a credenciais de um usuário, ou mesmo inibir determinadas operações. Enfim, toda regra relacionada a um objeto.
Ela também pode funcionar como uma ponte caso você decida, por exemplo, colocar a DAL em um Web Service, e permitir acesso a solução a partir de dispositivos móveis.
Para implementar a camada, clique em File>Add>New Project>Visual C#>Windows>Class Library para criar uma nova biblioteca DLL. Dê o nome de “BLL” para a Class Library e Location “C:\Multitier”. No Solution Explorer, modifique o nome do arquivo criado para “Revista.cs”.
Nota: Veja que todas as camadas da solução definem uma classe chamada Revista. Resolvi dar o mesmo nome, pois o namespace se encarrega de identificar a classe dentro do seu contexto e designar sua função. Por exemplo, DAL.Revista é a classe que persiste os dados, Types.Revista é a entidade do mapeamento O/R, BLL.Revista é a classe de negócio.
No Solution Explorer, dê um clique de direita no item References e adicione uma referência ao assembly Types que criamos anteriormente, a partir da aba Projects. Selecione também o assembly DAL.
Isso vai permitir que a BLL trabalhe com a entidade Revista criada anteriormente (Figura 5). Vai permitir também a comunicação com a camada de acesso a dados (DAL). Implemente a classe da BLL como mostrado na Listagem 4.
namespace BLL
{
public class Revista
{
public Types.Revistas Select()
{
DAL.Revista DAL_Rev = new DAL.Revista();
return DAL_Rev.Select();
}
public void Update(Types.Revista Obj)
{
DAL.Revista DAL_Rev = new DAL.Revista();
DAL_Rev.Update(Obj);
}
public void Insert(Types.Revista Obj)
{
DAL.Revista DAL_Rev = new DAL.Revista();
DAL_Rev.Insert(Obj);
}
public void Delete(Types.Revista Obj)
{
DAL.Revista DAL_Rev = new DAL.Revista();
DAL_Rev.Delete(Obj);
}
}
}
Note que, por enquanto, tudo que a camada faz é receber/enviar dados da camada de apresentação e “passar a bola” para a DAL. Mais adiante veremos como personalizar essa camada com validações.
Adaptando a DAL para trabalhar com objetos fortemente tipados
Agora precisamos alterar a DAL para que os métodos da classe Revista recebam e devolvam como parâmetro não mais tipos escalares, mas objetos. Por exemplo, o Select da DAL não retorna mais um DataSet, mas sim uma coleção de objetos fortemente tipados, nesse caso Revistas.
A nova implementação dele é mostrada na Listagem 5 (adicione a referência do projeto Types no projeto DAL).
public Types.Revistas Select()
{
Types.Revistas Objs = new Types.Revistas();
SqlConnection Con = new SqlConnection(ConStr);
SqlCommand Cmd = new SqlCommand("SelectRevistas",
Con);
Cmd.CommandType = CommandType.StoredProcedure;
Con.Open();
SqlDataReader rd = Cmd.ExecuteReader(
CommandBehavior.CloseConnection);
try {
while (rd.Read())
{
Types.Revista NewObj = new Types.Revista();
NewObj.NumEdicao = Convert.ToInt32(rd[0]);
NewObj.Capa = rd[1].ToString();
NewObj.Nivel = Convert.ToDouble(rd[2]);
Objs.Add(NewObj);
}
}
finally {
rd.Close();
}
return Objs;
}
Note que estamos usando um DataReader para ler os registros da tabela. Para cada registro encontrado, criamos um objeto do tipo Revista e adicionamos na coleção genérica que será devolvida como retorno do método. Note também que acessamos os campos do DataReader pelo índice, o que é mais rápido do que acessar pelo nome.
A Listagem 6 mostra como fica o Update. Agora ele recebe por parâmetro não mais os valores de tipos escalares, mas um objeto do tipo Types.Revista, nossa entidade de negócio. A outra modificação foi fazer os parâmetros obterem seus valores a partir desse objeto, o restante não muda. Utilize esse mesmo princípio para adaptar os métodos Insert e Delete.
public void Update(Types.Revista Obj)
{
SqlConnection Con = new SqlConnection(ConStr);
SqlCommand Cmd = new SqlCommand("UpdateRevista",
Con);
Cmd.CommandType = CommandType.StoredProcedure;
Cmd.Parameters.AddWithValue("@CAPA",Obj.Capa);
Cmd.Parameters.AddWithValue("@NIVEL",Obj.Nivel);
Cmd.Parameters.AddWithValue("@NUM_EDICAO",
Obj.NumEdicao);
Con.Open();
try {
Cmd.ExecuteNonQuery();
}
finally {
Con.Close();
}
}
Adaptando a interface para usar a BLL e tipos customizados
A interface da aplicação não se comunicará mais diretamente com a DAL, mas através da BLL, finalizando assim, nossa arquitetura exatamente como mostrado na Figura 1.
Além disso, ela não receberá mais dados em formato de DataSet e não chamará métodos da DAL. Toda a comunicação usará como “protocolo” o tipo customizado que definimos no assembly Types.
No Solution Explorer, no Web Site, adicione uma referência ao projeto Types e também BLL. Exclua as referências à DAL. O próximo passo é configurar o ObjectDataSource. No seu menu tasks, escolha a opção Configure Data Source.
Na primeira tela que aparece, dentre os vários objetos de negócio que aparecem, escolha nossa classe Revista que está dentro da BLL (Figura 6), pois essa é a única camada que a interface deve interagir.

Configure agora os métodos CRUD utilizados pelo ObjectDataSource. Note por exemplo que para o Update (Figura 7), o método que escolhemos não recebe mais como parâmetro os valores Num_Edicao, Capa e Nivel, mas um objeto do tipo Revista que já contém todos esses valores representados na forma de atributos.

O último passo é avisar o ObjectDataSource sobre o tipo de dado que estamos trabalhando. Isso é feito através da sua propriedade DataObjectTypeName, que deve ter seu valor configurado para "Types.Revista".
Nota: É necessário dar um Refresh Schema no GridView e DetailsView, ou mesmo redefinir a propriedade DataSourceID, para que os campos reflitam os atributos da classe de negócio e não mais os campos da tabela.
Pronto! Você já pode executar a aplicação e desfrutar de todas as vantagens de uma solução em camadas 100% Orientada a Objetos.
Incluindo regras de negócio
Vamos agora personalizar nossa BLL para que inclua regras de negócio. Vamos supor a seguinte regra. O índice que indica o nível de uma revista deve estar entre 1 e 3, porém, a revista tem que possuir um nível balanceado.
Digamos, a título de exemplo, que o nível deve estar entre 1.5 e 2.5, caso contrário ela estará com nível muito avançado ou muito introdutório. A Listagem 7 mostra como incluir essa validação na BLL, para o Insert (faça o mesmo para o Update).
public void Insert(Types.Revista Obj)
{
if (Obj.Nivel > 2.5)
throw new Exception("Revista muito introdutória");
if (Obj.Nivel < 1.5)
throw new Exception("Revista muito avançada");
DAL.Revista DAL_Rev = new DAL.Revista();
DAL_Rev.Insert(Obj);
}
Note que usamos um throw para levantar uma exceção, essa é a forma correta de levantar exceções em objetos de negócio. Jamais use caixas de mensagem ou JavaScript, pois isso depende do tipo de interface. A exceção levantada será propagada na pilha até o método que chamou o código, que por sua vez deve dar o tratamento adequado.
Quando usamos o DataSources, como o ODS, podemos manipular os eventos Inserted ou Updated para capturar possíveis exceções que tenham ocorrido durante a atualização. É o que fiz na Listagem 8. Note que o ASP.NET AJAX se encarrega de receber a exception e gerar uma caixa de erro ao invés da tradicional página de exceção do ASP.NET (Figura 8).
protected void ObjectDataSource1_Inserted(
object sender, ObjectDataSourceStatusEventArgs e)
{
if (!(e.Exception == null))
{
e.ExceptionHandled = true;
throw new Exception(
e.Exception.InnerException.Message);
}
}

Poderíamos implementar também essa mesma regra de negócio no próprio BD, se essa for a sua necessidade. Por exemplo, a Listagem 9 mostra como fazer essa mesma validação na procedure de inserção.
Testando a aplicação, note que a mensagem de erro é exibida sempre da mesma forma, não importa onde a exceção seja levantada.
ALTER procedure [dbo].[InsertRevista]
@NUM_EDICAO int,
@CAPA varchar(250),
@NIVEL decimal(18,2)
as
begin
if @NIVEL < 1.5
RaisError ('Revista muito avançada', 16, 1);
else
if @NIVEL > 2.5
RaisError ('Revista muito introdutória', 16, 1);
else
insert into REVISTAS values (@NUM_EDICAO, @CAPA,
@NIVEL)
end
E finalmente, poderíamos implementar a regra de negócio no próprio modelo, usando o modificador set do atributo Nivel. Veja como ficaria a mesma validação agora implementada na entidade (Listagem 10).
public double Nivel
{
get { return _Nivel; }
set {
if (value > 2.5)
throw new Exception(
"Revista muito introdutória");
if (value < 1.5)
throw new Exception("Revista muito avançada");
_Nivel = value;
}
}
É claro, essa validação poderia ainda ser feita na camada cliente, usando controles de validação. A questão aqui é que você tem inúmeras opções para escolher onde colocar determinada regra de negócio, analisando suas vantagens e desvantagens.
Conclusão
A solução que apresentei aqui se aplica a maioria dos projetos com ASP.NET, permitindo produtividade, modularização, flexibilidade, independência, ao mesmo tempo que desfruta dos melhores recursos da POO e do ASP.NET 2.0.
Dependendo do tipo de aplicativo que for desenvolver, uma determinada arquitetura deve ser escolhida, depende de você desenvolvedor analisar o que é melhor para o seu projeto.