Guardando imagens em bancos de dados com .NET
Dennes Torres (e-mail) possui as certificações MCAD, MCSD,MCSE e MCDBA. É diretor da Búfalo Informática (www.bufaloinfo.com.br) e líder do grupo de usuários devASPNet (www.devaspnet.com.br).
Uma pergunta constante de muitos programadores é como inserir e manipular imagens dentro de um banco de dados.
Uma excelente resposta seria: Não insira. Imagens ocupam muito espaço e demandam um gerenciamento muito cuidadoso. Podem prejudicar todo o acesso aos dados. É verdade, sim, que hoje os bancos de dados suportam isso com muito mais facilidade que antigamente, mas ainda assim a gravação de uma imagem junto com os registros de dados é a última opção que deve-se utilizar. Assim sendo, antes de vermos realmente como seria a gravação/leitura das imagens vejamos alternativas a esse cadastramento.
A principal alternativa é manter a imagem na forma de arquivo (.gif, por exemplo) e guardar no banco de dados a localização da imagem. Em uma aplicação desktop comum essa não é uma alternativa muito boa, pois fica muito complicado gerenciar os diversos arquivos de imagem de forma a mantê-los centralizados e acessíveis a todos os usuários. Hoje temos a opção de utilizar webServices entregando imagens, é uma opção.
Já para uma aplicação Web manter o arquivo da imagem e gravar apenas o caminho é uma excelente solução, especialmente porque o browser precisa de um arquivo para poder exibir a imagem.
Assim sendo, para exibir em uma página web uma imagem cujo caminho foi gravado dentro do banco de dados devemos montar uma tag IMG para a imagem. Ficaria algo como:
Observe que estamos considerando que o caminho está guardado em um campo chamado "CaminhoImagem" e estamos utilizando este campo para definir o SRC da tag IMG. É claro que você pode também definir as outras características da tag IMG.
Na verdade isso pode ser feito com mais facilidade dentro de uma DataGrid, por exemplo. Podemos inserir na dataGrid uma coluna que contenha o campo com a URL da imagem, então na Data Formating Expression montamos a seguinte expressão:
Desta forma, o campo será gerado no HTML como uma tag image chamando pela imagem na URL indicada pelo campo.
Isso claro também afeta a gravação. Em uma página de cadastramento de produtos, por exemplo, na qual o usuário precisará inserir a imagem de um produto, faz-se o upload do arquivo de imagem e grava-se no banco de dados o registro do novo produto com o path para o arquivo. Quando o produto for pesquisado pelos clientes a imagem estará disponível.
Recuperando a imagem do banco
Se realmente desejar gravar uma imagem em um banco de dados a questão será bem mais complicada tanto para gravação quanto para exibição da imagem.
O banco de dados PUBS, banco de exemplo contido no SQL Server, possui uma tabela chamada pub_info com um campo Logo. Esse campo Logo contém uma Gif, o logotipo da empresa. Vamos então fazer uma página ASP que exiba essa imagem.
Observe que para exibir uma imagem em uma página HTML é necessário ter uma tag IMG apontando para um arquivo que contenha apenas a imagem. Devido a essa característica, não é possível trazer dados e imagens juntos com uma única página ASPX. Imagine: Depois de fazer a exibição dos dados, como você exibiria a imagem? Qualquer tipo de exibição deixaria uma série de dados binários na página, misturados ao HTML, fazendo com que o browser não entenda nada do que deve ser exibido.
Desta forma, para podermos fazer a exibição de imagens precisaremos de um arquivos .ASPX capaz de ler uma imagem do banco de dados e devolve-la para o browser, sem nada a mais, apenas a imagem. Esse arquivo deverá ser utilizado na tag IMG da página como se fosse uma imagem. Veja um exemplo:
23 Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
24
25
26 Dim dr As OleDb.OleDbDataReader
27
28
29
30
31 cmdFoto.Parameters("pub_id").Value = Request.QueryString("id")
32
33
34 cn.Open()
35 dr = cmdFoto.ExecuteReader(CommandBehavior.SequentialAccess)
36
37
38 Response.ContentType = "image/jpeg"
39
40 dr.Read()
41 Response.BinaryWrite(dr("logo"))
42
43 dr.Close()
44 cn.Close()
45
46 End Sub
Utilizando ASP.NET Handlers
A estrutura acima, porém, não é a estrutura mais adequada para a devolução de imagens no ASP.NET. Para entender isso, é preciso entender um pouco melhor a organização do ASP.NET.
Tudo no ASP.NET são HTTP Handlers. Classes que implementam a interface IHttpHandler. Essa interface exige a implementação de um método ProcessRequest, responsável por todo o processamento da chamada.
Quando digo tudo, é isso mesmo: Cada página ASPX que você cria é um HTTP Handler. A classe page implementa a interface IhttpHandler. Ocorre que a Page é o mais complexo dos httpHandlers do ASP.NET : Uma página instancia cada um de seus controles child, faz a recuperação de viewState, dispara os eventos, e por aí vai.
Ocorre que para a recuperação de imagens não precisamos de nada disso. Então quando utilizamos uma página ASPX para a recuperação de imagens estamos gerando uma sobrecarga de processamento totalmente desnecessária.
Fiz testes de recuperação de imagens com uma página ASPX e um .ASHX, um HTTP Handler no ASP.NET . Realizando 3000 mil requisições na página (nesta mesma aplicação acima), houve uma diferença de 11 segundos em vantagem do HTTP Handler.
O Visual Studio não dá muito suporte para a criação de ASP.NET HTTP Handlers, mas podemos fazer isso de forma bem simples:
. Criamos um novo webForm, porém ao definirmos o nome indicamos a extensão .ASHX
. Apagamos todo o conteúdo do arquivo e geramos uma tag @WebHandler, como no exemplo abaixo:
. Crie uma nova Classe
. Implemente a interface IhttpHandler
. Na tag @WebHandler utilize o atributo Class para apontar para a nova classe
. Pronto, a estrutura geral do handler está criada, agora basta programar o método ProcessRequest.
51 Public ReadOnly Property IsReusable() As Boolean Implements System.Web.IHttpHandler.IsReusable
52
53
54 Get
55 Return False
56 End Get
57 End Property
58
59 Public Sub ProcessRequest(ByVal context As System.Web.HttpContext) Implements System.Web.IHttpHandler.ProcessRequest
60
61
62 InitializeComponent()
63 Dim dr As OleDb.OleDbDataReader
64 Dim buffer(99) As Byte
65 Dim inicio As Integer
66 Dim lido As Integer
67 cmdFoto.Parameters("pub_id").Value = context.Request.QueryString("id")
68
69
70 cn.Open()
71 dr = cmdFoto.ExecuteReader(CommandBehavior.SequentialAccess)
72
73
74 context.Response.ContentType = "image/jpeg"
75
76 inicio = 0
77 dr.Read()
78 context.Response.BinaryWrite(dr("logo"))
79
80
81 dr.Close()
82 cn.Close()
83 End Sub
Acessando imagens bmp em bancos Access/SQL Server
Imagens BMP são gravadas de forma diferente em campos BLOB. Estas imagens são gravados como objeto OLE. Essa forma de gravação faz com que seja inserido um cabeçalho nos campos, cabeçalho este que pode variar de acordo com o tipo de informação contida no campo.
Para imagens .BMP é inserido um cabeçalho de 78 bytes. Então o truque para acesso as imagens é desprezar os 78 bytes iniciais e exibir o restante. Veja como ficaria o código:
86 Response.ContentType = "image/bmp"
87 Dim foto As Byte()
88 Dim foto2 As Byte()
89 cmdFoto.Parameters("employeeid").Value = Request.QueryString("id")
90
91
92 cn.Open()
93 foto = cmdFoto.ExecuteScalar()
94 cn.Close()
95 ReDim Preserve foto2(foto.Length - 78)
96
97 foto.Copy(foto, 78, foto2, 0, foto.Length - 78)
98
99 Response.BinaryWrite(foto2)
Esse código acima roda com o banco northwind, buscando as fotos dos funcionários da tabela Employees.
Gravação de imagens na base de dados
Tão simples como a leitura de imagens é a gravação de imagens na base de dados. Em ambiente Web a complexidade adicional que esta gravação tem é a necessidade de um upload feito para o servidor web.
Vamos ver como fica um exemplo para cadastrar um logo de editoras na base PUBS. Os dados de editoras são divididos em 2 tabelas, Publishers e Pub_Info, sendo que o logo fica separado na tabela Pub_Info.
Vamos montar a inserção de dados nas duas tabelas. Veja como ficam as instruções INSERT :
INSERT INTO publishers
(pub_id, pub_name, city, state, country)
VALUES (?, ?, ?, ?, ?)
INSERT INTO pub_info (pub_id, logo) VALUES (?, ?)
Utilizei nesta tela acima um FileField para fazer o upload, coloquei o FileField como RunAt=Server. Veja como ficou o código do botão "Cadastrar":
66 Private Sub cmdCadastrar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdCadastrar.Click
67
68
69 cmdAdicionar.Parameters("pub_id").Value = txtPubId.Text
70 cmdAdicionar.Parameters("pub_name").Value = txtPubName.Text
71 cmdAdicionar.Parameters("city").Value = txtCity.Text
72 cmdAdicionar.Parameters("state").Value = txtState.Text
73 cmdAdicionar.Parameters("country").Value = txtCountry.Text
74
75 cmdAdicionarFoto.Parameters("pub_id").Value = txtPubId.Text
76
77 Dim pf As HttpPostedFile
78 Dim st As IO.Stream
79 Dim br As IO.BinaryReader
80
81 pf = txtFoto.PostedFile
82 st = pf.InputStream
83
84 br = New IO.BinaryReader(st)
85
86 cmdAdicionarFoto.Parameters("Logo").Value = br.ReadBytes(st.Length)
87
88 CN.Open()
89 cmdAdicionar.ExecuteNonQuery()
90 cmdAdicionarFoto.ExecuteNonQuery()
91 CN.Close()
92 lblResultado.Text = "Gravação concluida com sucesso!"
93 End Sub
O ponto chave deste código é a atribuição ao parâmetro "Logo": Basta atribuir um array de bytes para fazer a gravação. Utilizei a classe BinaryReader para simplificar a leitura do arquivo de imagem na forma de um array de bytes.
Com estes exemplos acredito que você terá facilidade em manipular imagens em bancos de dados.