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:

artigo200605-01.jpg

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

artigo200605-03.jpg

artigo200605-02.jpg

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 (?, ?)

artigo200605-04.jpg

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.