Definir permissão para Stored Procedures no SQL Server

Muitas pessoas têm me perguntado, recentemente, como definir permissões de execução para todas as Stored Procedures de um Banco de Dados SQL Server 2005/2008 de maneira automática, sem que seja necessário utilizar a interface gráfica do SQL Management Studio que requer que você defina as permissões das SPs uma por uma.

No SQL Server, para definir permissão de execução para uma Stored Procedure tudo o que você precisa fazer é utilizar a sintaxe:

GRANT EXECUTE ON [dbo].[sproc] TO [Usuário]

* [dbo].[sproc] é o nome da Stored Procedure
* [Usuário] é o nome da conta de usuário que receberá a permissão.

Entretanto, durante o desenvolvimento de um sistema computacional com um mínimo de complexidade, são criadas inúmeras Stored Procedures, o que torna a tarefa de executar a permissão em massa para os procedimentos armazenados uma atividade cansativa.

Para resolver este problema, preparei o seguinte script que irá gerar, automaticamente, os comandos GRANT para todas as Stored Procedures definidas no seu Banco de Dados:

-- Thiago Marotta Couto
-- October, 04 - 2009
-- http://isbyte.com/	

DECLARE @userName AS NVARCHAR(100);
DECLARE @namePattern AS NVARCHAR(10);

	-- Nome do Usuário
	SET @userName = N'livebackup';

	-- Filtrar Stored Procedures
	-- Exemplos:
	--		%	Todas Stored Procedures
	--		spc%	Todas Stored Procedures que começam com "spc"
	--		%SP	Todas Stored Procedures que terminam com "SP"
	SET @namePattern = N'%';

	-- Define o Banco de Dados
	USE [master]

	SELECT
		N'GRANT EXECUTE ON '
		+ QUOTENAME(OBJECT_SCHEMA_NAME([object_id])) + '.'
		+ QUOTENAME([name])
		+ N' TO '
		+ QUOTENAME(@userName)
		+ N';'
	FROM sys.procedures
	WHERE [name] LIKE @namePattern;

Após executar o script, copie o resultado (lista de GRANTs) e execute na sua base de dados.

SQL Server - Passo a Passo SQL Server 2008 Para Leigos SQL Guia Rápido

Como criar propriedades em UserControls do ASP.NET

Em todos os projetos ASP.NET que estive envolvido em minha vida, quase nunca me deparei com implementações idênticas de propriedades de ASP.NET UserControls. E não estou me referindo a propriedades de complexidade avançada ou média, falo sobre o tipo mais básico de propriedade, aquelas utilizadas para persistir uma informação na ViewState.

Particularmente esta é uma questão que, embora não muito importante, sempre me deixou muito curioso, mas que, por estar envolvido em questões mais relevantes, nunca reservei um tempo para pesquisar a respeito e descobrir a melhor maneira de persistir e recuperar uma informação na ViewState do ASP.NET através de uma propriedade.

Depois de muito pesquisar pelo Google sem encontrar um padrão de implementação bem definido, resolvi abrir o .NET Reflector e investigar como a Microsoft implementou esta tarefa em seus WebControls.

Durante esta análise do código-fonte do .NET Framework, percebi que existe um certo padrão em todo o código-fonte, onde a Microsoft define propriedades de objetos seguindo o esquema abaixo:

 

Propriedades “System.String”:

[Bindable(true, BindingDirection.TwoWay),
PersistenceMode(PersistenceMode.EncodedInnerDefaultProperty),
Editor("System.ComponentModel.Design.MultilineStringEditor,System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor)),
Localizable(true),
WebCategory("Appearance"),
DefaultValue(""),
WebSysDescription("TextBox_Text")]

public virtual string Text
{
    get
    {
        string str = (string) this.ViewState["Text"];
        if (str != null)
        {
            return str;
        }
        return string.Empty;
    }
    set
    {
        this.ViewState["Text"] = value;
    }
}

* Propriedade Text do objeto System.Web.UI.WebControls.TextBox.

 

Propriedades “System.Boolean”:

[DefaultValue(false),
Bindable(true,
BindingDirection.TwoWay),
Themeable(false),
WebSysDescription("CheckBox_Checked")]

public virtual bool Checked
{
    get
    {
        object obj2 = this.ViewState["Checked"];
        return ((obj2 != null) && ((bool) obj2));
    }
    set
    {
        this.ViewState["Checked"] = value;
    }
}

* Propriedade Checked do objeto System.Web.UI.WebControls.CheckBox.

 

Propriedades “System.Int32”:

[WebSysDescription("TableCell_RowSpan"),
WebCategory("Layout"),
DefaultValue(0)]

public virtual int RowSpan
{
    get
    {
        object obj2 = this.ViewState["RowSpan"];
        if (obj2 != null)
        {
            return (int) obj2;
        }
        return 0;
    }
    set
    {
        if (value < 0)
        {
            throw new ArgumentOutOfRangeException("value");
        }
        this.ViewState["RowSpan"] = value;
    }
}

* Propriedade RowSpan do objeto System.Web.UI.WebControls.TableCell.

Armazenar e recuperar imagens no Banco de Dados

Durante o desenvolvimento do sistema com o qual trabalho atualmente, identificamos que o usuário teria a necessidade de subir arquivos de imagem (jpeg, gif, png ou bmp) para o servidor da aplicação através da interface do sistema. O sistema deveria permitir, por sua vez, que estes arquivos de imagem (banners) fossem acessados por outros usuários através de uma sub-url da aplicação, como:

http://www.sistema.com.br/banners/nome_do_banner.jpeg

Adicionalmente, precisaríamos contabilizar as impressões destas imagens, ou seja, gravar a quantidade de vezes que a imagem do banner foi acessada e por quem, para que o “causador” do acesso fosse recompensado financeiramente por ter exibido aquela imagem em seu site.

Com estes requisitos, precisávamos escolher qual abordagem seria mais interessante:

  • Armazenar as imagens diretamente no Banco de Dados, juntamente com suas informações de controle;
  • Armazenar as imagens no disco e referenciar seu caminho no Banco de Dados, juntamente com suas informações de controle;

Baseando-me em um artigo da Microsoft, em parte da documentação do novo tipo FILESTREAM do SQL Server 2008 e alguns testes de desempenho com os dois casos apresentados, concluí que o mais interessante para este sistema seria armazenar as imagens (que normalmente possuíam menos de 256 Kbytes) no Banco de Dados da aplicação.

Além das vantagens de desempenho descritas pelo artigo da MS, utilizando a estratégia de armazenar os arquivos no Banco de Dados teríamos mais dois benefícios de grande importância para este projeto específico:

  • Não necessidade de definir permissão de gravação em uma pasta do projeto;
  • Durante a publicação do sistema, onde os arquivos do servidor de produção são substituídos com os arquivos do servidor de homologação/desenvolvimento, a pasta “/Banners” não seria sobrescrita com uma versão desatualizada, excluindo os últimos banners adicionados pelos usuários.

Por esta razão, a estratégia escolhida foi o armazenamento dos arquivos de imagem no Banco de Dados, que descrevo em detalhes nas próximas linhas:

Para demonstrar a prática que deveria ser implementada pelos desenvolvedores, preparei um exemplo simples utilizando C# e SQL Server 2008, que pode ser baixado integralmente aqui.

No exemplo, foquei-me exclusivamente em demonstrar como a gravação dos arquivos deveria ser feita no Banco de Dados e como o acesso de tais arquivos deveria ser realizado. Demais detalhes, como contabilização de acessos e checagem de segurança (verificar se o usuário possui permissão para exibir aquela imagem) foram omitidos para que o código fosse simplificado e tivesse um tom didático.

A explicação, por sua vez, foi dividida nas seguintes etapas:

  • Estrutura do Banco de Dados;
  • Interface da Aplicação;
  • Camada de Acesso a Dados;
  • Camada Controller: Interligando tudo!;
  • IHttpHandler: Escutando o endereço “../banners/*.jpeg”;

Estrutura do Banco de Dados

Inicialmente, foi criada a seguinte tabela no Banco de Dados:

ImagesDB

Na coluna [Images].[Name], foi criado um índice para garantir a unicidade das imagens. Com isso, teríamos a certeza de que duas imagens não compartilhariam o mesmo nome.

O campo [Images].[BinaryContent] é o campo que será utilizado para guardar o conteúdo do arquivo. O seu tipo foi declarado como varbinary(MAX), como sugerido pelo artigo da Microsoft citado anteriormente.

Também foi criado um campo [MIMEType], que irá armazenar o tipo do arquivo armazenado no Banco de Dados, uma vez que o usuário poderá fazer upload de arquivos Jpeg, Gif e outros formatos e precisamos saber, na hora de carregá-los na tela, qual formato estamos exibindo.

Interface da Aplicação

Com o Banco de Dados criado, seria necessário criar a interface para que o usuário pudesse efetuar o upload dos arquivos. O código fonte da interface (webform) ficou assim:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="IHttpHandlerSample._Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server" method="post" enctype="multipart/form-data">
<div>
<h1>Welcome to IHttpHandler example!</h1>
<p>Try to load an Jpeg image on this server by upload the file and typing the image name in the address bar of your browser.</p>
<p><input id="cFileUpload" type="file" runat="server" /></p>
<p><input id="cSubmit" type="submit" value="Upload!" /></p>
</div>
</form>
</body>
</html>

Notem que, na interface, utilizamos a TAG <input id=”cFileUpload” type=”file” runat=”server” /> (sic), responsável por desenhar na tela o controle através do qual o usuário poderá selecionar, em seu computador, o arquivo que deseja subir. Para que possamos tratar o envio deste arquivo, manipulando-o pelo código-fonte da aplicação, foi necessário adicionar o atributo runat=”server” no controle HTML, que faz com que este controle, embora seja HTML, seja gerenciado pelo servidor ASP.NET (sem isso não conseguiríamos “pegar” o arquivo enviado pelo usuário.

Da mesma maneira, foi necessário modificar algumas características do formulário (<form>), para que fosse possível postar o arquivo. O método passou a ser “post” e o tipo de codificação passou a ser “multipart/form-data”.

A interface da aplicação de exemplo ficou, portanto, assim:

ImageHandler - Interface

Camada de Acesso a Dados

Com o Banco de Dados já preparado para receber os arquivos e a interface permitindo que o usuário suba os arquivos que achar conveniente, é hora de criar o método que grava, efetivamente, a imagem no Banco de Dados.

Porém, antes disso, vamos a uma explicação básica da forma como a aplicação de exemplo está organizada:

Para ser possível codificar a aplicação de maneira que pudéssemos separar claramente os papeis internos do software, foi adicionada uma simulação de “camada de acesso a dados” (DataLayer) através de duas classes principais:

  • DataLayer.DAL.ImagesDAL

Objeto responsável por executar operações (Insert, Select, Update & Delete) na tabela [Images] criada no Banco de Dados. O sufixo DAL em seu nome significa “Data Access Layer”.

  • DataLayer.ObjectModel.Image

Objeto que representa a instância de uma imagem na aplicação. Este objeto possui cinco propriedades que são exatamente as mesmas colunas da tabela [Images] do Banco de Dados.

Com estes dois objetos em mente, a estratégia para adicionar o arquivo no Banco de Dados, portanto é a seguinte:

1. Capturar o arquivo enviado pela interface de usuário;

2. Criar um “modelo de objeto” (DataLayer.ObjectModel.Image) que represente o arquivo enviado pelo usuário, de forma que este modelo contenha seu nome, tamanho, MIME Type e conteúdo do arquivo;

3. Executar a inserção deste arquivo no Banco de Dados, utilizando, para tanto, o objeto DataLayer.DAL.ImagesDAL, que é o responsável por executar inserções e outras operações no Banco de Dados.

Apresento aqui, portanto, o “modelo de objeto”, que representa um arquivo de imagem no código-fonte da nossa aplicação:

using System;

namespace IHttpHandlerSample.DataLayer.ObjectModel
{
public class Image
{
public Guid ID { get; set; }
public string Name { get; set; }
public int Size { get; set; }
public string MIMEType { get; set; }
public byte[] BinaryContent { get; set; }
}
}

Como podem notar esta é uma classe muito simples, desenvolvida com o objetivo singular de armazenar as informações de uma única imagem. E que, portanto, dispensa apresentação.

Como nesta aplicação só iremos inserir um objeto no Banco de Dados e depois consultá-lo, sem a necessidade de realizar remoções ou atualizações no objeto já existente, ao classe DataLayer.DAL.ImagesDAL foi simplificada para conter somente os métodos que iremos precisar. Segue o código fonte:

using System;
using System.Data.SqlClient;
using System.Configuration;

namespace IHttpHandlerSample.DataLayer.DAL
{
public class Images : IDisposable
{
SqlConnection _connection;

public Images()
{
//Initializes the database connection object.
this._connection = new SqlConnection();

//Configures the connection string of the database.
this._connection.ConnectionString = ConfigurationManager.ConnectionStrings[@"ImagesDB"].ConnectionString;
}

private bool TryConnect()
{
//If connection is closed or broken...
if (this._connection.State == System.Data.ConnectionState.Closed ||
this._connection.State == System.Data.ConnectionState.Broken)
{
try
{
//Close the broked connections.
this._connection.Close();

//Try to connect to the database.
this._connection.Open();

//Database connected!
return true;
}
catch
{
//ERROR: The database can't be opened.
return false;
}
}
else
//Database is already connected.
return true;
}

private void Disconnect()
{
//Closes the database connection.
this._connection.Close();
}

public DataLayer.ObjectModel.Image Select(string name)
{
SqlCommand command;
SqlDataReader dataReader;
DataLayer.ObjectModel.Image result = null;

//Check if any necessary parameters is null or empty.
if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(@"name");

using (command = new SqlCommand())
{
//Configures the database connection.
command.Connection = this._connection;

//Defines the SELECT sql that will find from the database the requested file.
command.CommandText = @"
SELECT
[ID],
[Name],
[Size],
[MIMEType],
[BinaryContent]
FROM [Images]
WHERE
[Name] = ISNULL(@fileName, [Name])";

//Defines that this is an SQL text command.
command.CommandType = System.Data.CommandType.Text;

//Configures the parameters and values of file that will be searched.
command.Parameters.Add(@"@fileName", System.Data.SqlDbType.NVarChar).Value = name;

if (this.TryConnect() == true)
{
try
{
using (dataReader = command.ExecuteReader(System.Data.CommandBehavior.CloseConnection))
{
if (dataReader.HasRows)
{
while (dataReader.Read())
{
result = new DataLayer.ObjectModel.Image()
{
ID = dataReader.GetGuid(0),
Name = dataReader.GetString(1),
Size = dataReader.GetInt32(2),
MIMEType = dataReader.GetString(3),
BinaryContent = (byte[])dataReader.GetSqlBinary(4)
};
}

//Returns the collection of found images.
return result;
}
else
return null;
}
}
catch
{
//ERROR: Select fail.
return null;
}
finally
{
//Closes the database connection.
this.Disconnect();
}
}
else
//Database is not connected.
return null;
}
}

public bool Insert(DataLayer.ObjectModel.Image image)
{
SqlCommand command;

//Check if any necessary parameters is null or empty.
if (image == null) throw new ArgumentNullException(@"image");
if (image.ID == null) throw new ArgumentException(@"The ID property of the Image object can not be null.");
if (string.IsNullOrEmpty(image.Name)) throw new ArgumentException(@"The Name property of the Image object can not be null.");
if (string.IsNullOrEmpty(image.MIMEType)) throw new ArgumentException(@"The MIMEType property of the Image object can not be null.");
if (image.BinaryContent == null) throw new ArgumentException(@"The BinaryContent property of the Image object can not be null.");

using (command = new SqlCommand())
{
//Configures the database connection.
command.Connection = this._connection;

//Defines the INSERT sql that will inject the file into the database.
command.CommandText = @"
INSERT INTO [Images] (
[Name],
[Size],
[MIMEType],
[BinaryContent])
VALUES (
@fileName,
@fileSize,
@fileType,
@fileContent)";

//Defines that this is an SQL text command.
command.CommandType = System.Data.CommandType.Text;

//Configures the parameters/values that will be inserted.
command.Parameters.Add(@"@fileName", System.Data.SqlDbType.NVarChar).Value = image.Name;
command.Parameters.Add(@"@fileSize", System.Data.SqlDbType.Int).Value = image.Size;
command.Parameters.Add(@"@fileType", System.Data.SqlDbType.NVarChar).Value = image.MIMEType;
command.Parameters.Add(@"@fileContent", System.Data.SqlDbType.VarBinary).Value = image.BinaryContent;

if (this.TryConnect() == true)
{
try
{
//Try to execute INSERT command.
if (command.ExecuteNonQuery() == 1)
//Insert executed!
return true;
else
//Insert fail.
return false;
}
catch
{
//ERROR: Insert fail.
return false;
}
finally
{
//Closes the database connection.
this.Disconnect();
}
}
else
//Database is not connected.
return false;
}
}

#region IDisposable Members

public void Dispose()
{
//Disposes the database connection object (the connection will be closed).
this._connection.Dispose();
}

#endregion
}
}

Como pode ser notado, não há nenhuma prática incomum na classe anterior: Ela é composta pelos dois métodos públicos principais Insert e Select, que iremos utilizar nesta aplicação.

O método Insert recebe, como parâmetro de entrada, um objeto do tipo DataLayer.ObjectModel.Image, que é lido internamente e inserido no Banco de Dados.

De forma semelhante, o método Select recebe, como parâmetro, uma System.String contendo o nome do arquivo que será procurado no Banco de Dados. Uma vez encontrado, suas informações são utilizadas para preencher uma instância do objeto DataLayer.ObjectModel.Image, que é devolvido como retorno do método.

Camada Controller: Interligando tudo!

Uma vez que nossos objetos que efetuam as operações já estejam prontos, basta que os mesmos sejam interligados pela camada Controller, que no nosso caso é simplesmente o code-behind do webform que utilizamos para construir a interface, ou seja, o arquivo Default.aspx.cs:

protected void Page_Load(object sender, EventArgs e)
{
HttpPostedFile postedFile;
DataLayer.DAL.Images imagesDAL;
DataLayer.ObjectModel.Image image;

bool isInserted;

if (IsPostBack)
{
//If image has been uploaded...
if (cFileUpload.PostedFile != null)
{
//Gets the reference of the posted file.
postedFile = cFileUpload.PostedFile;

#region "Creating The Representation Of Posted File"
//Initializes an representation of file that will description the posted file.
image = new DataLayer.ObjectModel.Image();

//Stores the ID of the posted file.
image.ID = System.Guid.NewGuid();

//Stores the name of the posted file.
image.Name = System.IO.Path.GetFileName(postedFile.FileName);

//Stores the size of the posted file.
image.Size = postedFile.ContentLength;

//Stores the MIME Type of the posted file.
image.MIMEType = postedFile.ContentType;

//Stores the binary content of the posted file.
image.BinaryContent = new byte[postedFile.ContentLength];
postedFile.InputStream.Read(image.BinaryContent, 0, postedFile.ContentLength);
#endregion

//Initializes Images adapter.
using (imagesDAL = new DataLayer.DAL.Images())
{
//Inserts posted image into the database.
isInserted = imagesDAL.Insert(image);
}

if (isInserted)
//Open posted file.
Response.Redirect(string.Format("~/{0}", image.Name));
else
//Shows the error message to the user.
Response.Write("The upload fail. Try again!");
}
}
}

No evento “OnLoad” do webform, foi adicionado, portanto, o código que faz o controle de tudo: Pega o arquivo enviado pela interface e envia à camada de acesso a dados de forma que ela entenda.

O código anterior, ao detectar que a inserção no Banco de Dados foi concluída com sucesso, redireciona o usuário para o endereço onde o mesmo visualizará o Banner sendo recuperado do Banco de Dados.

A próxima etapa trata exatamente disto: Como ouvir um determinado diretório para devolver as imagens guardadas no Banco de Dados?

IHttpHandler: Escutando o endereço “../banners/*.jpeg”;

Uma vez armazenadas no Banco de Dados, as imagens precisam ser recuperadas quando solicitadas pelos usuários. Esta solicitação, por sua vez, se dá através do acesso a um endereço como:

http://www.sistema.com.br/banners/nome_do_banner.jpeg

Para tanto, foi necessário implementar uma classe especial, cujo trabalho é “observar” um determinado endereço e, caso seja feita alguma requisição, processar e responder.

Esta classe, que na aplicação de exemplo chama-se Handlers.ImageHandler, precisa, obrigatoriamente, implementar a interface System.Web.IHttpHandler, conforme o código fonte abaixo:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;</span>

namespace IHttpHandlerSample.Handlers
{
public class ImageHandler : IHttpHandler
{
#region IHttpHandler Members
public bool IsReusable
{
get { return false; }
}

public void ProcessRequest(HttpContext context)
{
string fileName = null;
string referrer;
DataLayer.DAL.Images imagesDAL;
DataLayer.ObjectModel.Image image;

//Clears all content output from the buffer stream.
context.Response.Clear();

//Gets the filename requested.
fileName = System.IO.Path.GetFileName(context.Request.Path);

/* ---------------------------------------------------------------------------
* Gets the URL of the client's previous request that linked the current URL.
*
* This information will be used for security checks and accounting of access.
* E.g.: This user has permission to access the resource?
*       How many times the user accessed the resource?
*/
referrer = context.Request.UrlReferrer.ToString();
// ---------------------------------------------------------------------------

using (imagesDAL = new DataLayer.DAL.Images())
{
//Searches requested filename in the database.
image = imagesDAL.Select(fileName);
}

if (image != null)
{
//Configures the HTTP MIME Type of the output stream.
context.Response.ContentType = image.MIMEType;

//Configures the content of the HTTP response.
context.Response.OutputStream.Write(image.BinaryContent, 0, image.Size);
}
else
//File not found.
context.Response.StatusCode = 404;

//Sends all currently buffered output to the client and stops execution of the page.
context.Response.End();
}
#endregion
}
}

O método mais importante desta classe é o ProcessRequest, que receberá a requisição assim que ela for efetuada.

Para que a aplicação entenda que a nova classe Handlers.ImageHandler é a responsável por processar toda requisição de arquivos de imagem, foi necessário adicionar, no Web.Config da aplicação, a seguinte linha:

<add verb="GET" type="IHttpHandlerSample.Handlers.ImageHandler" path="*.jpg,*.jpeg,*.png,*.gif,*.bmp"/>

A linha de configuração deverá ser inserida dentro da sessão <system.web>.

Como escrever Casos de Uso

Nenhum sistema existe isoladamente. Todo sistema interessante interage com atores humanos ou atores autômatos que utilizam esse sistema para algum propósito. Estes atores, por sua vez, esperam que o sistema apresente o comportamento que foi previsto durante a sua concepção.

É exatamente neste ponto que entra em ação o nosso objeto de estudo, o Caso de Uso (do inglês Use Case – ou simplesmente UC).

Os Casos de Uso foram introduzidos pela UML (Linguagem Unificada de Modelagem – Do inglês Unified Modeling Language), que é uma linguagem criada para auxiliar as pessoas envolvidas no processo de desenvolvimento de um software no sentido de ajudá-las a comunicar-se entre si sobre as características e especificações deste novo sistema.

Hoje vamos falar especificamente sobre os Use Cases, que são ferramentas utilizadas para captar o comportamento pretendido do sistema que está sendo desenvolvido sem que seja necessário especificar como esse comportamento será implementado. Este conceito é extremamente importante, por isso, vou repeti-lo citando os próprios autores da UML:

“… Casos de Uso especificam o comportamento desejado; eles não determinam como esse comportamento será executado.” – [BOOCH, RUMBAUGH & JACOBSON, 2005]

A vantagem disso é simples: Permitir que você se comunique com os envolvidos no processo de desenvolvimento de software (gerentes, analistas, desenvolvedores, patrocinadores e stakeholders) sem se preocupar com detalhes. Estes detalhes aparecerão, mas os Casos de Uso permitem que você focalize primeiro os pontos que considera de maior risco.

Conceber Use Cases para especificar o comportamento de um sistema sem se preocupar em como isso “será transformado em software” é de fundamental importância para validar o sistema sem que seja necessário construí-lo.

Não utilizar este tipo de abordagem durante a fase de projeto de um novo sistema poderá resultar em um alto índice de “re-trabalho”, uma vez que sem a ajuda de uma ferramenta adequada para avaliar o sistema como um todo antes do seu desenvolvimento efetivo poderá exigir mudanças mais drásticas que o habitual durante o período de desenvolvimento (quando a viabilidade e organização do projeto já deveriam estar concluídas).

É importante notar, porém, que conceber Use Cases não é o mesmo que “desenhar” diagramas interligando “balões” e “bonecos palito”. É necessário tomar cuidado para que “Casos de Uso” não sejam confundidos com “Diagramas de Caso de Uso”. Embora sejam ferramentas complementares, não são ferramentas idênticas, ao contrário, são conceitos bem distintos.

Os Diagramas de Caso de Uso são importantes para que os envolvidos na construção do novo sistema percebam, através de uma visão espacial, quais são os fluxos que compõe todo o sistema. Porém, Diagramas de Caso de Uso apenas representam graficamente (mapeiam) quais Atores utilizam quais Casos de Uso. Portanto, a descrição de um Caso de Uso é, na verdade, o principal elemento desta ferramenta. Diagramas de Caso de Uso sem descrição são como caixas sem conteúdo organizadas por data: Você consegue encontrar a caixa correspondente ao período que deseja, mas não tem acesso ao documento que descreve aquilo que realmente procura.

Parafraseando mais uma vez os autores da UML, um Caso de Uso nada mais é que uma descrição de um conjunto de seqüências de ações, inclusive variantes, que um sistema executa para produzir um resultado de valor observável por um ator, como no exemplo abaixo:

Fazer Logon

1. Usuário digita o endereço do site na barra de endereços do navegador;

2. Usuário entra com seu login e senha nos campos disponíveis na tela e clica, em seguida, no botão “Entrar”;

3. Sistema verifica se o login e a senha digitada são válidos para acessar o sistema;

4. Sistema conclui que os dados informados estão corretos e redireciona o usuário para a página principal do site.

É importante notar que em momento algum o texto acima descreveu como o sistema consultou se os dados de acesso do usuário estavam corretos, assim como não demonstrou qual foi o recurso técnico utilizado para redirecionar o usuário para um novo endereço. Ao invés disso, o texto abordou apenas a visão geral do processo, ocultando todos os detalhes tecnológicos, o que tornou a leitura mais agradável e compreensível para qualquer pessoa com um mínimo de intimidade com sistemas computacionais.

Ao fazer uma modelagem é importante manter clara a separação entre a visão interna e externa. Você pode especificar o comportamento de um Caso de Uso pela descrição do fluxo de eventos de maneira suficientemente clara para que alguém de fora possa compreendê-lo facilmente.

Um Caso de Uso, portanto, não é a forma mais recomendada de comunicar à equipe de desenvolvimento sobre os detalhes de implementação de um determinado projeto. Para isso existem outras ferramentas e diagramas da própria UML (Diagrama de Classes, Diagrama de Objetos, Diagrama de Interação, Diagrama de Artefatos, etc), que tornam a comunicação consideravelmente mais clara e organizada entre os envolvidos na implementação. Por outro lado, os Use Cases têm sua importância para a comunicação com a equipe interna, pois através deles é possível entender rapidamente o comportamento esperado e as regras de negócio do sistema. Adicionalmente, os UCs são ótimas fontes para a geração de Casos de Teste (Do inglês Test Cases), que têm como objetivo a documentação dos testes que precisam ser realizados (e quais resultados precisam ser atingidos) para que uma determinada funcionalidade do sistema tenha sua implementação considerada adequada, ou seja, atinja o seu comportamento esperado.

Uma vez que um Caso de Uso é modelado para representar o comportamento de um determinado processo, verificamos facilmente que o Caso de Uso descreve um conjunto de seqüências e não apenas uma seqüência isolada, pois seria impossível representar todos os detalhes de um processo interessante em apenas uma seqüência. Por exemplo, em um sistema de recursos humanos você poderá encontrar um Caso de Uso chamado “Contratar Empregado”, que representa o fluxo principal para a contratação de um colaborador para a empresa. Porém esta regra de negócio geral poderá ter muitas variações, como contratar uma pessoa de outra empresa, transferir uma pessoa de um departamento para o outro (algo comum em multinacionais) ou contratar um estrangeiro residente no local (o que incluirá suas próprias regras especiais). Cada uma dessas variantes (chamadas de cenários na UML) poderá ser expressa em uma seqüência diferente.

Segundo Booch, Rumbaugh e Jacobson, os cenários estão para os Casos de Uso assim como as instâncias estão para as classes.

É recomendado que descrevamos, primeiramente, a seqüência principal, que também é chamada, por alguns autores, de “dia perfeito”, “dia de sucesso” ou “fluxo básico”.

Por exemplo, no contexto de um sistema de caixa eletrônico, você poderá descrever o caso de uso “Sacar Dinheiro” da seguinte forma, conforme sugere o exemplo apresentado por Roques:


<Caso de Uso> Sacar Dinheiro

Autor: Pascal Roques
Data de Criação: 02/03/2002

Última Atualização: 18/07/2009
Responsável pela Atualização: Thiago Marotta Couto

Versão: 2.2

Atores:

  • Dono do Cartão (primário)
  • Entidade Financeira (secundário)

Pré-condições:

  • O caixa eletrônico está abastecido.
  • Nenhum cartão está inserido na leitora de cartões do caixa eletrônico.

Fluxo Principal:

1. O Dono do Cartão insere seu SmartCard na leitora de cartões do caixa eletrônico.

2. O caixa eletrônico verifica que o cartão inserido é do tipo SmartCard.

3. O caixa eletrônico pergunta ao Dono do Cartão qual é o seu número PIN.

4. O Dono do Cartão digita seu número PIN.

5. O caixa eletrônico verifica que o PIN numérico digitado pelo Dono do Cartão é o mesmo PIN numérico armazenado no CHIP do SmartCard.

6. O caixa eletrônico envia uma requisição de autorização para a Entidade Financeira.

7. A Entidade Financeira autoriza a transação para aquele cartão, informando o limite diário de saque.

8. O caixa eletrônico pergunta ao Dono do Cartão qual valor ele deseja sacar.

9. O Dono do Cartão digita o valor que deseja sacar.

10. O caixa eletrônico confirma que o valor a ser sacado está dentro do limite diário.

11. O caixa eletrônico ejeta o SmartCard.

12. O Dono do Cartão retira seu SmartCard na leitora.

13. O caixa eletrônico ejeta o dinheiro.

14. O Dono do Cartão retira o dinheiro.

Fluxos Alternativos:

A1: Número PIN incorreto nas duas primeiras tentativas.

A seqüência A1 começa após o passo 4 do Fluxo Principal.

5. O caixa eletrônico verifica, nas duas primeiras tentativas, que o PIN numérico digitado pelo Dono do Cartão não é o mesmo PIN numérico armazenado no CHIP do SmartCard.

6. O caixa eletrônico informa, ao Dono do Cartão, que o número PIN está incorreto.

7. O caixa eletrônico armazena a tentativa mal sucedida no CHIP do SmartCard.

O cenário volta para o passo 3.

A2: O valor requisitado para saque é maior que o limite diário.

A seqüência A2 começa após o passo 9 do Fluxo Principal.

11. O caixa eletrônico informa ao Dono do Cartão que o valor requisitado é maior que o limite diário para saque.

O cenário volta para o passo 8.

Fluxos de Erro:

E1: Número PIN incorreto na terceira tentativa.

A seqüência E1 começa após o passo 4 do Fluxo Principal.

5. O caixa eletrônico verifica, pela terceira vez, que o PIN numérico digitado pelo Dono do Cartão não é o mesmo PIN numérico armazenado no CHIP do SmartCard.

6. O caixa eletrônico confisca o SmartCard.

7. A Entidade Financeira é informada sobre a falha de autenticação.

O Caso de Uso falha.

E2: Cartão inválido.

A seqüência E2 começa após o passo 1 do Fluxo Principal.

2. O caixa eletrônico informa ao Dono do Cartão que seu cartão não é válido (não é um SmartCard; não pode ser lido; está expirado; etc.).

O Caso de Uso falha.

E3: Limite diário não autorizado.

A seqüência E3 começa após o passo 6 do Fluxo Principal.

7. A Entidade Financeira rejeita a transação para a quantia especificada.

8. O Caixa eletrônico ejeta o SmartCard.

O Caso de Uso falha.

E4: O cartão não é retirado pelo Dono do Cartão.

A seqüência E4 começa após o passo 11 do Fluxo Principal.

12. Após 15 segundos, o caixa eletrônico confisca o SmartCard.

13. A Entidade Financeira é informada sobre o cartão confiscado.

O Caso de Uso falha.

Pós-Condições:

  • A gaveta de dinheiro do caixa eletrônico contém uma quantidade de notas inferior à quantidade estabelecida no início deste Caso de Uso (o número exato de notas faltantes depende diretamente da quantidade de transações realizadas e o valor de cada operação de saque).


Notem que, além de descrever os fluxos dos Casos de Uso também estão presentes, no exemplo anterior, seções (conceitos) como “Pré-Condições”, “Pós-Condições” e “Atores”. É importante acrescentá-los na descrição dos Use Cases para que o leitor tenha uma visão completa de todo o processo, ou seja, como o cenário estava antes da execução do processo, como o cenário ficará em seguida e quais atores interagiram com aquele Caso de Uso.

Adicionalmente é uma boa prática disponibilizar um cabeçalho com os dados de criação do Caso de Uso, com informações básicas de quem criou e atualizou o documento e suas respectivas datas.

Para que os artigos não fiquem demasiadamente extensos (se é que este já não ficou), vamos parar por aqui e deixar para os próximos artigos os conceitos de Generalização/Especialização, Inclusão e Extensão. Futuramente também trataremos especificamente dos Diagramas de Caso de Uso, frutos de várias confusões sobre a forma correta de “interligá-los”.

Referências Bibliográficas:

BOOCH, Grady; RUMBAUGH, James; JACOBSON, Ivair. UML: Guia do Usuário. Rio de Janeiro, 2005.

ROQUES, Pascal. UML in Practice – The Art of Modeling Software Systems Demonstrated throught Worked Examples and Solutions. Paris, França. 2001.

Localization em ASP.NET

No início desta semana, um colega de trabalho me perguntou como funcionam os conceitos Globalization e Localization do ASP.NET e, como não é comum encontrar pessoas que os utilizam na prática, surgiu a idéia de falar um pouquinho sobre eles aqui no blog e explicar do que se trata e como agir para colocá-los em prática.

Hoje vou falar um pouco sobre o Localization!

O que é Localization (ou Localização)?

Localization é a palavra escolhida para definir um conceito fantástico que se resume em tornar sua página Web independente do idioma do visitante, ou seja, caso um brasileiro visite seu site, o verá em português, caso o visitante seja um norte-americano, visualizará em inglês.

Antigamente, para realizar tal tarefa o desenvolvedor poderia optar por, basicamente, duas alternativas:

· Criar uma versão da página para cada idioma;

· Desenvolver um mecanismo que verificasse o idioma do usuário e buscasse em um arquivo ou banco de dados os textos corretos a serem exibidos, exibindo-os na página.

A primeira opção, embora seja a mais simples de executar, duplicaria o tamanho total do seu Website, além da necessidade de se criar uma página principal onde o usuário escolheria, no início da navegação, com qual idioma ele gostaria de prosseguir.

Notada por muitos como a solução mais elegante, a segunda opção passou então a ser o foco dos arquitetos de sistemas voltados para a Web, uma vez que, dentre várias vantagens, seria possível modificar ou incluir um idioma sem a necessidade de acessar o código-fonte da aplicação, ou seja, dando segurança para o projeto e independência para as equipes de desenvolvimento e tradução.

Como a necessidade de “localização” é comum a quase todos os projetos de software (seja para Web ou não), a Microsoft apresentou este mecanismo já implementado no .NET Framework, sendo possível tornar sua aplicação independente de idioma com apenas alguns procedimentos e cliques (sem digitar uma linha sequer de código).

Como utilizar Localization na minha página ASP.NET?

Hoje em dia ficou fácil adicionar suporte a um determinado idioma na sua aplicação Web desenvolvida em ASP.NET. Veja como fazer, passo a passo:

· No Visual Studio 2008, clique em File, New, Project;

· Em Project Types, escolha sua linguagem de desenvolvimento favorita e em Templates escolha ASP.NET Web Application.

  • Adicione alguns controles à página Default.aspx (ver imagem):

· Através do Solution Explorer, clique com o botão direito em Default.aspx e clique em View Designer no menu flutuante;

· Arraste do ToolBox um controle Label para a página, definindo seu texto para “Seja bem vindo!” e seu ID para “cLabelWelcome”;

· Arraste do ToolBox um controle Button para a página, definindo seu texto para “Clique aqui para entrar” e seu ID para “cButtonEnter”.

  • Crie o arquivo de Resource com a língua padrão (ver imagem):

· Clique no menu Tools, Generate Local Resource.

  • Crie o arquivo de Resource com a língua alternativa:

· Através do Solution Explorer, clique com o botão direito no arquivo Default.aspx.resx que está localizado dentro da pasta App_LocalResources criada pelo Visual Studio 2008 e clique em Copy;

· Clique com o botão direito sobre a pasta App_LocalResources e clique em Paste;

· Renomeie o arquivo Copy of Default.aspx.resx para Default.aspx.en-us.resx e clique duas vezes sobre ele para abri-lo;

· Substitua o texto “Clique aqui para entrar” por “Click here to enter”, assim como o texto “Seja bem vindo!” por “Welcome!”;

· No menu do Visual Studio 2008, clique em File, Save App_LocalResources\Default.aspx.en-us.resx.

Pronto! Sua aplicação já oferece suporte para o idioma português (idioma padrão) e inglês… Para adicionar novos idiomas, repita a etapa 4, mas ao renomear o arquivo, substitua “en-us” pelo código da língua desejada, como “en-ca” para inglês canadense ou “es-mx” para espanhol mexicano.

No momento em que sua página for acessada, o ASP.NET verificará o idioma marcado no navegador do usuário e encontrará o arquivo de Resource correto, que será aplicado à página para exibir os textos corretamente. Caso o arquivo correto não seja encontrado (usuário esteja utilizando um idioma que não possui um arquivo de Resource para ele), a página irá utilizar o arquivo padrão (aquele que não tem o código do idioma).

Para testar sua aplicação, execute o projeto e verifique o idioma em que foram exibidos os textos do site. Em seguida, no Internet Explorer, vá em Tools (Ferramentas), Internet Options (Opções de Internet) e clique no botão Languages (Idiomas). Com o botão Add (Adicionar), escolha a opção English (United States) [en-US] e clique em Ok. Com ajuda do botão Move Up (Acima), coloque a nova opção como a primeira da lista e dê Ok nas duas janelas abertas.

localization04

Clique em Refresh (Atualizar) ou pressione F5 no navegador para ver a página aplicando os textos conforme a nova linguagem definida no browser

Bom, é isso pessoal. Em breve falaremos um pouco sobre Globalization. Até lá!

Performance em Silverlight – Dica Rápida #3

Utilize o método Double.ToString(CultureInfo.InvariantCulture) ao invés de simplesmente Double.ToString().

O método Double.ToString(IFormatProvider) que recebe CultureInfo.InvariantCulture como argumento é otimizado para obter melhor performance.

Em geral, quando não se deseja exibir um dado formatado ao usuário ou obter dados sensíveis à culturas diferentes (como, por exemplo, durante a comparação de duas strings), a melhor prática, segundo a Microsoft, é a utilização de Double.ToString(CultureInfo.InvariantCulture).

Se, porém, sua aplicação irá exibir números ao usuário e é necessária a adequação de tais números à cultura local, você deverá utilizar Double.ToString(IFormatProvider) recebendo CultureInfo.CurrentCulture como argumento ou simplesmente Double.ToString(), pois as duas formas adéquam o resultado à cultura corrente.

Fonte: MS Help (Performance Tips)

Performance em Silverlight – Dica Rápida #2

Se você deseja simplesmente exibir ou ocultar um objeto na tela e não precisa deixá-lo parcialmente transparente, utilize a propriedade Visibility ao invés de Opacity.

A propriedade Opacity acarreta em alto custo computacional, pois o objeto continuará sendo tecnicamente “renderizado” e testado pelo Hit Test.

Segundo a Microsoft, modificar a propriedade Visibility para Collapsed é a prática mais recomendável para evitar estes custos caso seu interesse não seja tornar o objeto translúcido, mas escondê-lo na tela.

Fonte: MS Help (Performance Tips)

Performance em Silverlight – Dica Rápida #1

Desenvolver animações que envolvem aumentar ou diminuir o tamanho de um texto pode consumir muitos recursos do sistema. Isso acontece porque o Silverlight arredonda os cantos de todo objeto texto.

Se você animar o tamanho de um texto (utilizando o objeto Transform ou a propriedade FontSize), o Silverlight irá arrendondar os cantos do texto à cada frame, o que possui um alto custo computacional e pode resultar em erros durante a exibição dos frames.

Se a sua aplicação requer a alteração dinâmica de uma grande quantidade de texto, a Microsoft sugere a utilização de uma imagem vetorial que represente o texto a ser modificado.

Fonte: MS Help (Performance Tips)

Compartilhar pasta na rede com C#

O Microsoft .NET Framework, embora seja bem amplo, ainda não possui APIs que permitem criar compartilhamentos de pastas e arquivos no Windows. Todavia, é possível criar um novo compartilhamento (e várias outras tarefas específicas do Sistema Operacional) através de chamadas a objetos WMI (Windows Management Instrumentation).

Para este artigo, preparei uma função que compartilha um diretório específico em uma rede Microsoft. Embora seja possível definir várias propriedades avançadas do compartilhamento, decidi omitir as configurações de segurança (direitos de acesso) para tornar o código mais simples e compreensível a qualquer um, portanto, se você tiver interesse em criar compartilhamentos com direitos de acesso que não sejam somente-leitura, deixe uma mensagem aqui no blog ou leia a documentação da classe WMI que vamos utilizar (Win32_Share).

Então vamos ao que interessa. Primeiramente, adicione ao seu projeto uma referência à namespace System.Management, clicando com o botão direito em “References” e, em seguida, em “Add Reference…”. Selecione “System.Management” na janela que abrir e clique no botão “OK”.

Primeiramente é aconselhável que criemos dois Enums com alguns valores pré-definidos, para que não fiquemos interagindo com nossa função através de números hexadecimais.

        /// 
        /// Types of resource that will be shared.
        /// 
        protected enum ShareType : uint
        {
            DiskDrive = 0x0,
            PrintQueue = 0x1,
            Device = 0x2,
            IPC = 0x3,
            DiskDriveAdmin = 0x80000000,
            PrintQueueAdmin = 0x80000001,
            DeviceAdmin = 0x80000002,
            IPCAdmin = 0x80000003
        }
        /// 
        /// Result types of creation of the share.
        /// 
        protected enum ShareResult : uint
        {
            Success = 0,
            AccessDenied = 2,
            UnknownFailure = 8,
            InvalidName = 9,
            InvalidLevel = 10,
            InvalidParameter = 21,
            DuplicateShare = 22,
            RedirectedPath = 23,
            UnknownDeviceOrDirectory = 24,
            NetNameNotFound = 25
        }

Notem que as duas Enums acima não são Enums de números inteiros simples, mas de inteiros “sem sinal”, ou seja, “uint”. Isso porque os valores pré-definidos são grandes demais para serem armazenados em variáveis do tipo inteiro (padrão das Enums) e serão sempre positivas, por isso fez-se necessário herdar, para cada Enum, o tipo “uint”.

E, finalmente, a nossa função que instancia a classe “Win32_Share” do WMI e faz a chamada ao método “Create”, para criar o compartilhamento do recurso:

/// 
/// 
/// 
protected static ShareResult ShareFolder(string path, string shareName, ShareType type, uint? maximumAllowed, string description, string password)
        {
            System.Management.ManagementClass wmiWin32_Share;
            System.Management.ManagementBaseObject inputParameters;
            System.Management.ManagementBaseObject outputParameters;

            //Verifies if the received arguments are correct.
            if ((path == null) || (path == string.Empty))
                throw new ArgumentNullException("The path argument can not be null or empty.");
            if ((shareName == null) || (shareName == string.Empty))
                throw new ArgumentNullException("The shareName argument can not be null or empty.");
            try
            {
                //Instantiate the "Win32_Share" WMI class.
                wmiWin32_Share = new System.Management.ManagementClass(@"Win32_Share");

                //Recover the parameters of "Create" method.
                inputParameters = wmiWin32_Share.GetMethodParameters(@"Create");

                //Defines mandatory parameters.
                inputParameters.SetPropertyValue(@"Path", path);
                inputParameters.SetPropertyValue(@"Name", shareName);
                inputParameters.SetPropertyValue(@"Type", Convert.ToUInt32(type));

                //Defines optional parameters if received.
                if (maximumAllowed != null) inputParameters.SetPropertyValue(@"MaximumAllowed", maximumAllowed);
                if (description != null) inputParameters.SetPropertyValue(@"Description", description);
                if (password != null) inputParameters.SetPropertyValue(@"Password", password);

                //Invokes the "Create" method.
                outputParameters = wmiWin32_Share.InvokeMethod(@"Create", inputParameters, null);

                //Returns the result of "Create" method.
                return (ShareResult)outputParameters.GetPropertyValue(@"ReturnValue");
            }
            catch
            {
                //An unknown error occurred.
                return ShareResult.UnknownFailure;
            }
        }

É importante notar que somente administradores do sistema ou usuários com credenciais Communication, Print ou Server têm permissão para executar o método “Create” da classe “Win32_Share” e, conseqüentemente, nossa função. Operadores de impressão somente poderão adicionar filas de impressão, assim como operadores de comunicação somente poderão adicionar filas de comunicação. Para testar o funcionamento destas permissões, execute a função recém criada com o Visual Studio sem elevação de permissão e, em seguida, em modo administrador.

Para executar a função, utilize a seguinte sintaxe:

            ShareResult result;

            //Try to share MyPictures user folder.
            result = ShareFolder(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures),
                "MyPictures Folder", ShareType.DiskDrive, null, "My personal photos.", null);

            MessageBox.Show("The result of ShareFolder method: " + result.ToString());

As classes WMI permitem que você execute tarefas bem interessantes, além de recuperar informações e características do computador que talvez sejam possíveis somente através delas. Não deixe de conferir a documentação completa com todas as classes, propriedades e métodos aqui no site do MSDN.

Qualquer dúvida utilize o espaço para comentários do blog. Boa diversão ;)

Utilizando CACHE do ASP.NET com MS ACCESS, em C#

Desempenho de software é um assunto que sempre desperta o meu interesse, e foi graças a isso que acabei descobrindo um dos recursos mais fantásticos disponíveis no ASP.NET, o Cache.

Todo desenvolvedor de software sabe que uma das partes mais lentas de uma aplicação é o acesso ao disco rígido, isso porque ainda trabalhamos com um modelo de disco rígido baseado em cabeça mecânica de leitura de dados e outras características que não vem ao caso. O problema é que muitas operações costumeiras durante o desenvolvimento de um software estão diretamente ligadas ao acesso a disco, como leitura e gravação de arquivos, acesso a banco de dados, etc. E foi graças a esse tipo de “gargalo” da computação que um ser iluminado do passado pensou em um recurso que ficou conhecido como Cache. Três vivas para o inventor do Cache: IoI IoI IoI.

A palavra Cache, na computação, diz respeito a uma quantidade de memória utilizada para armazenar dados temporariamente, de modo a fornecê-los rapidamente quando solicitados pelo processador sem a necessidade de acessar sua origem (muitas vezes localizada no disco rígido, que é cerca de dez vezes mais lento que a memória principal).

O Cache do ASP.NET não é diferente, é um recurso que tem como função armazenar objetos na memória principal do computador. Adicionalmente, o Cache permite a adição de dependências ao objeto guardado, de forma que se a dependência sofrer alguma alteração, o objeto contido no Cache é automaticamente destruído para que não se torne inconsistente.

Com ele, podemos criar aplicações mais rápidas, principalmente se tais aplicações necessitam de muitos acessos a um banco de dados, por exemplo.

Para explicar como funciona tudo isso, vou mostrar como desenvolver uma aplicação que carrega um banco de dados MS Access na memória principal do computador, de modo que não seja necessário acessar o disco rígido para ler as informações contidas nele caso já tenha sido carregado para o Cache. Adicionalmente esta aplicação deve manter a consistência dos dados, ou seja, se o banco de dados for atualizado pela própria aplicação ou por terceiros, o sistema deverá reconhecer automaticamente e atualizar também o Cache.

Para esta aplicação utilizei um banco de dados bem simples, criado no MS Access 2007, que consiste em uma única tabela onde será gravado o primeiro e o último nome de algumas pessoas.

 accessusertable

Obs.: Nomeie o banco de dados como “database.accdb” e adicione-o à pasta “App_Data” do seu projeto, no Visual Studio, pois o código fonte foi desenvolvido partindo do princípio que o nosso banco está localizado lá dentro.

Primeiramente, vamos criar um método que acessa o banco de dados MS Access 2007 e retorna um DataSet já carregado com as informações que desejamos:

        private System.Data.DataSet GetData(string accessFileName, string query)
        {
            System.Text.StringBuilder connectionString;
            System.Data.DataSet dataSet;

            //Initializes System.Text.StringBuilder.
            connectionString = new System.Text.StringBuilder();

            //Creates connection string based on file name.
            connectionString.AppendFormat(@"Provider=Microsoft.ACE.OLEDB.12.0;");
            connectionString.AppendFormat(@"Data Source= {0};", accessFileName);

            using (System.Data.OleDb.OleDbConnection connection = new System.Data.OleDb.OleDbConnection(connectionString.ToString()))
            {
                //Creates System.Data.OleDb.OleDbCommand.
                System.Data.OleDb.OleDbCommand command = new System.Data.OleDb.OleDbCommand(query, connection);

                //Creates System.Data.OleDb.OleDbDataAdapter.
                System.Data.OleDb.OleDbDataAdapter adapter = new System.Data.OleDb.OleDbDataAdapter(command);

                //Initializes DataSet.
                dataSet = new System.Data.DataSet();

                //Fills DataSet.
                adapter.Fill(dataSet);
            }

            //Returns data.
            return dataSet;
        }

Agora vem a mágica: Um método que recebe e adiciona à memória principal o objeto que será armazenado em Cache, uma chave identificadora e o arquivo físico ao qual o objeto está ligado em uma relação de dependência:

        private void SetCache(string cacheKey, object item, string fileNameDependency)
        {
            System.Web.Caching.CacheDependency dependency;

            //Creates dependency based on received file name.
            dependency = new System.Web.Caching.CacheDependency(fileNameDependency);

            //Stores data in the ASP.NET Cache.
            Cache.Insert(cacheKey, item, dependency);
        }

E finalmente, vamos interligar tudo no manipulador do evento Page_Load da nossa aplicação Web:

        protected void Page_Load(object sender, EventArgs e)
        {
            const string cFileName = @"~/App_Data/database.accdb";
            const string cQuery = @"SELECT [ID], [UserName], [LastName] FROM [User]";
            const string cCacheKey = @"User";
            System.Data.DataSet dsUser;

            //Gets data from the ASP.NET Cache.
            dsUser = (System.Data.DataSet)Cache.Get(cCacheKey);

            //If data are not present...
            if (dsUser == null)
            {
                //Gets data from database.
                dsUser = this.GetData(Server.MapPath(cFileName), cQuery);

                //Saves data in the ASP.NET Cache.
                this.SetCache(cCacheKey, dsUser, Server.MapPath(cFileName));
            }

            //Shows data.
            GridView1.DataSource = dsUser;
            GridView1.DataBind();
        }

Reparem que para simplificar a aplicação, foram criadas três constantes, sendo a primeira o caminho do arquivo de banco de dados, a segunda a query que será executada e a terceira uma chave única para identificar o nosso objeto no Cache do ASP.NET.

Para visualizar o resultado na tela, também foi adicionado um GridView à página, com o ID “GridView1”, onde serão exibidas as informações do banco.

Se preferir, clique aqui para baixar a solução completa (código fonte e banco de dados).

Para testar o funcionamento, insira um breakpoint no início do método Page_Load e rode a aplicação. Executando linha a linha, repare que inicialmente o nosso objeto ainda não existe no Cache e, por isso, retornará um DataSet nulo. Desta forma, a aplicação terá que acessar o banco de dados e trazer, pela primeira vez, as informações para o Cache.

Depois da primeira execução, dê um reload no navegador e o método Page_Load será executado novamente. Desta vez, o objeto estará presente no Cache e não será necessário acessar o banco de dados para exibir as informações na tela.

Depois da segunda execução, insira um novo registro no banco de dados (lembre-se de salvar e fechar a tabela aberta, caso faça a inserção diretamente pelo MS Access 2007). Dê um reload no navegador e note que ao procurar o nosso objeto no Cache ele não existe mais. Isso porque o ASP.NET detectou que o arquivo sofreu alteração e removeu da memória as informações armazenadas. Neste caso, a aplicação irá retornar ao banco de dados para recarregar os dados na memória.

Fantástico, não?!

Essa tecnologia pode ser aplicada a qualquer tipo de banco de dados baseado em arquivo, como Firebird, SQLite ou mesmo XML, arquivos MS Excel ou de texto. Existe também a possibilidade de utilizar este recurso em conjunto com o SQL Server 2005 ou superior, mas isso eu mostro em outro artigo… ;)

Baixar o código fonte completo.