none
Mostrar de manera óptima imágenes en base de datos SQL Server que fueron guardadas como VARBINARY(MAX) en ASP.Net C# RRS feed

  • Pregunta

  • Buen dia a todos, tengo el siguiente problema el cual primero describo el ambiente.

    1.- Base de datos SQL Server 2012:
    Tabla: FotosPrueba

    CREATE TABLE [dbo].[FotosPrueba](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [Foto] [varbinary](max) NOT NULL
    ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]


    2.- ASP.Net C# FW 4.5

         Diseño donde muestro las imagenes (observar la zona <ItemTemplate>)

    <asp:ListView ID="ListImagenes" runat="server">
    
                            <LayoutTemplate>
                                <table id="Table2" runat="server" class="table-responsive">
                                    <tr id="Tr1" runat="server">
                                        <td id="Td1" runat="server">
                                            <table id="itemPlaceholderContainer" runat="server" class="table table-bordered table-hover table-condensed table-responsive" border="3" style="background-color: #FFFFFF; border-collapse: collapse; border-color: #999999; border-style: none; border-width: 1px; font-family: Verdana, Arial, Helvetica, sans-serif;">
                                                <tr id="Tr2" runat="server">
    
                                                    <th id="Th1" runat="server" style="text-align: center;">ID Imagen</th>
                                                    <th id="Th2" runat="server" style="text-align: center;">Imagen</th>
    
                                                </tr>
                                                <tr id="itemPlaceholder" runat="server">
                                                </tr>
                                            </table>
                                        </td>
                                    </tr>
    
                                </table>
    
                            </LayoutTemplate>
    
                            <ItemTemplate>
                                <tr class="success">
                                    <td>
                                        <asp:Label ID="Label1" runat="server" Text='<%#   Eval("ID") %>'></asp:Label>
                                    </td>
                                    <td>
                                        <asp:Image ID="Image1" ImageUrl='<%# "data:image/jpg;base64," + Convert.ToBase64String((byte[])Eval("foto"))  %>' runat="server" Style="width: 50px; height: 50px" data-container="body" data-toggle="popover" data-placement="right" />
                                        <script>
                                            $(document).ready(function () {
    
                                                $('<%#  "#ListImagenes_Image1_" + Container.DisplayIndex %>').popover({
                                                    html: true,
                                                    trigger: 'hover',
                                                    content: '<img src="' + '<%# "data:image/jpg;base64," + Convert.ToBase64String((byte[])Eval("foto")) %>' + '" id="popover_img" Style="width: 500px" />'
                                                }).hover(function () {
                                                    $('#popover_img').attr('src', $(this).data('img'));
                                                });
    
                                                $('<%#  "#ListImagenes_Image1_" + Container.DisplayIndex %>').mouseover(function () {
                                                    $('<%#  "#ListImagenes_Image1_" + Container.DisplayIndex %>').popover('show')
                                                });
    
                                                $('<%#  "#ListImagenes_Image1_" + Container.DisplayIndex %>').mouseout(function () {
                                                    $('<%#  "#ListImagenes_Image1_" + Container.DisplayIndex %>').popover('hide')
                                                 });
                                            });
    
                                        </script>
                                    </td>
                                </tr>
    
    
                            </ItemTemplate>
    
                        </asp:ListView>
    
    3.- Codigo:
    
     protected void Page_Load(object sender, EventArgs e)
            {
                if (!IsPostBack)
                {
                    LlenarListImagenes();
    
    }
            }
    
     public void LlenarListImagenes()
            {
                ListImagenes.DataSource = DataAccess.SelectFotoPrueba(null); // Le paso Null y me trae todas las imagenes de base de datos
                ListImagenes.DataBind();
            }
    
     public DataTable SelectFotoPrueba(int? id)
            {
                SqlDataAdapter adapter = new SqlDataAdapter();
                SqlConnection connection = new SqlConnection(ConfigurationManager.AppSettings.Get("EscuelaSeguraConnectionString").ToString());
                SqlCommand sqlCommand;
                DataTable dt;
                dt = new DataTable();
    
                try
                {
                    connection.Open();
                    sqlCommand = new SqlCommand("Sp_SelectFotoPrueba", connection);
    
                    sqlCommand.CommandType = CommandType.StoredProcedure;
                    sqlCommand.Parameters.AddWithValue("@ID", id);
    
                    adapter.SelectCommand = sqlCommand;
    
    
                    adapter.Fill(dt);
    
                    connection.Close();
                    connection.Dispose();
                    connection = null;
    
                    return dt;
    
                }
                catch
                {
                    return null;
                }
                finally
                {
                    dt.Dispose();
                }

    PREGUNTA:

    ¿Cual es la Manera mas Optima para poder mostrar estas imágenes cuando su cantidad es mayor a 500 imagenes?

    cuando tengo 500 o mas imagenes la pagina me da 

    System.OutOfMemoryException


    sábado, 30 de abril de 2016 14:05

Respuestas

  • Si usas el dataurl, el problema es que los bytes de todas las imágenes se van a incrustar dentro del html de la página. Si tienes muchas imágenes, y encima son grandes, no es de extrañar que algo se desborde y dé el OutOfMemoryException.

    Como alternativa, usa para las imágenes una Url "corriente" que apunte a un Generic Handler (.ashx) dentro de tu proyecto, y que éste sirva el contenido de la imagen. Lógicamente en cada url pondrías algún parámetro para indicar cuál es la imagen deseada, y el .ashx recuperaría ese dato del QueryString.

    Esto ocasionará que por cada imagen se haga un GET del navegador al servidor, y si hay 500 imágenes pues son 500 GETs. Pero por lo menos el HTML se conseguirá cargar en el navegador, y después éste irá pidiéndole al servidor las imágenes poco a poco.

    sábado, 30 de abril de 2016 18:03
  • Si no olvidé nada, sería un handler así:

        public class GetImage : IHttpHandler
        {
            #region IHttpHandler Members
    
            public bool IsReusable
            {
                get { return true; }
            }
            private byte[] SelectFotoPrueba(int? id)
            {
                //Los datatable son malos, jeje.  Usamos un datareader solamente.
                using (SqlConnection connection = new SqlConnection(ConfigurationManager.AppSettings.Get("EscuelaSeguraConnectionString").ToString()))
                {
                    using (SqlCommand sqlCommand = new SqlCommand("Sp_SelectFotoPrueba", connection))
                    {
                        connection.Open();
                        sqlCommand.CommandType = CommandType.StoredProcedure;
                        sqlCommand.Parameters.AddWithValue("@ID", id == null ? DBNull.Value : (object)id);
                        using (SqlDataReader r = sqlCommand.ExecuteReader())
                        {
                            if (r.Read())
                            {
                                return (byte[])r["Foto"];
                            }
                            else
                            {
                                //O generamos una excepción o simplemente devolvemos nulo.  Prefiero la excepción.
                                throw new ArgumentException("El ID proporcionado no se ha encontrado en base de datos.");
                            }
                        }
                    }
                }
            }
            public void ProcessRequest(HttpContext context)
            {
                //Obtengo la clave primaria que identifica
                int imgId = Int32.Parse(context.Request.QueryString["id"]);
                try
                {
                    byte[] datos = SelectFotoPrueba(imgId);
                    context.Response.Clear();
                    context.Response.ContentType = "image/jpg";
                    context.Response.BinaryWrite(datos);
                    context.Response.End();
                }
                catch (Exception)
                {
                    context.Response.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
                }
            }
    

    Aquí uso una versión de la función que obtiene datos de la base de datos que no usa DataTable pues son más problema que solución.  Además su función hace algo que no me gusta nada, que es un catch que esconde cualquier error que se ha producido.

    Luego lo que resta es registrar el handler en web.config.  Le dejé el enlace porque depende de la versión de IIS.

    Ah, y antes había un archivo con extensióno ASHX para los handlers, pero eso ya desapareció en versiones recientes de ASP.net; ahora es solamente una clase que implementa IHttpHandler y el registro en web.config.


    Jose R. MCP
    Code Samples

    • Marcado como respuesta jesusgomes domingo, 8 de mayo de 2016 1:36
    domingo, 1 de mayo de 2016 0:41
    Moderador

Todas las respuestas

  • Si usas el dataurl, el problema es que los bytes de todas las imágenes se van a incrustar dentro del html de la página. Si tienes muchas imágenes, y encima son grandes, no es de extrañar que algo se desborde y dé el OutOfMemoryException.

    Como alternativa, usa para las imágenes una Url "corriente" que apunte a un Generic Handler (.ashx) dentro de tu proyecto, y que éste sirva el contenido de la imagen. Lógicamente en cada url pondrías algún parámetro para indicar cuál es la imagen deseada, y el .ashx recuperaría ese dato del QueryString.

    Esto ocasionará que por cada imagen se haga un GET del navegador al servidor, y si hay 500 imágenes pues son 500 GETs. Pero por lo menos el HTML se conseguirá cargar en el navegador, y después éste irá pidiéndole al servidor las imágenes poco a poco.

    sábado, 30 de abril de 2016 18:03
  • Amigo disculpa pudiera usted hacerme un ejemplo aplicado a mi problema  del Generic Handler (.ashx) y explicarme que es?

    la idea es que en ningun momento la imagen se guarde físicamente en el servidor

    gracias de antemano por responder

    sábado, 30 de abril de 2016 18:39
  • Si no olvidé nada, sería un handler así:

        public class GetImage : IHttpHandler
        {
            #region IHttpHandler Members
    
            public bool IsReusable
            {
                get { return true; }
            }
            private byte[] SelectFotoPrueba(int? id)
            {
                //Los datatable son malos, jeje.  Usamos un datareader solamente.
                using (SqlConnection connection = new SqlConnection(ConfigurationManager.AppSettings.Get("EscuelaSeguraConnectionString").ToString()))
                {
                    using (SqlCommand sqlCommand = new SqlCommand("Sp_SelectFotoPrueba", connection))
                    {
                        connection.Open();
                        sqlCommand.CommandType = CommandType.StoredProcedure;
                        sqlCommand.Parameters.AddWithValue("@ID", id == null ? DBNull.Value : (object)id);
                        using (SqlDataReader r = sqlCommand.ExecuteReader())
                        {
                            if (r.Read())
                            {
                                return (byte[])r["Foto"];
                            }
                            else
                            {
                                //O generamos una excepción o simplemente devolvemos nulo.  Prefiero la excepción.
                                throw new ArgumentException("El ID proporcionado no se ha encontrado en base de datos.");
                            }
                        }
                    }
                }
            }
            public void ProcessRequest(HttpContext context)
            {
                //Obtengo la clave primaria que identifica
                int imgId = Int32.Parse(context.Request.QueryString["id"]);
                try
                {
                    byte[] datos = SelectFotoPrueba(imgId);
                    context.Response.Clear();
                    context.Response.ContentType = "image/jpg";
                    context.Response.BinaryWrite(datos);
                    context.Response.End();
                }
                catch (Exception)
                {
                    context.Response.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
                }
            }
    

    Aquí uso una versión de la función que obtiene datos de la base de datos que no usa DataTable pues son más problema que solución.  Además su función hace algo que no me gusta nada, que es un catch que esconde cualquier error que se ha producido.

    Luego lo que resta es registrar el handler en web.config.  Le dejé el enlace porque depende de la versión de IIS.

    Ah, y antes había un archivo con extensióno ASHX para los handlers, pero eso ya desapareció en versiones recientes de ASP.net; ahora es solamente una clase que implementa IHttpHandler y el registro en web.config.


    Jose R. MCP
    Code Samples

    • Marcado como respuesta jesusgomes domingo, 8 de mayo de 2016 1:36
    domingo, 1 de mayo de 2016 0:41
    Moderador
  • [...] antes había un archivo con extensióno ASHX para los handlers, pero eso ya desapareció en versiones recientes de ASP.net;

    Jose, ¿podrías aclarar un poco más a qué versiones te refieres? A mí me sigue saliendo el .ashx en la lista de plantillas disponibles, usando Visual Studio 2015 y una aplicación WebForms (como ha de ser la que indicaba el OP puesto que habla de un ItemTemplate).

    Por cierto, el Response.End() solo es necesario cuando haces esto mismo desde un .aspx (que es menos eficiente que el Handler), para evitar que se sirva en la respuesta el html que haya en el diseño del aspx. Pero en el Handler no se necesita.

    domingo, 1 de mayo de 2016 10:19
  • Hola Alberto.  ¿De verdad?  Hace mucho no programo webforms, pero cuando hay preguntas aquí como esta voy y trabajo un proyecto de prueba y viera que no me agrega el ASHX.  La primera vez me extrañó pero imaginé que tal vez habían eliminado el archivo ASHX y simplemente se habían movido a clase + configuración.  Estoy usando VS 2013, UPdate 5.  ¿Será que está trabajando mal?  Le soy sincero:  Di el ejemplo del handler y no lo probé.  ¿Será que funciona sin el archivo ASHX?  Yo pensaría que sí.

    Viera que hice una búsqueda de los templates instalados en mi VS 2013 Update 5 y aparentemente hay 2:  ASP.NET handler, y Generic Handler.  El primero es el que utilicé para responder aquí; el segundo sí crea un archivo ASHX y tiene un Code Behind que es básicamente lo mismo que yo escribí con la otra opción.  O sea, el segundo parece que me da una implementación básica de la interfaz IHttpHandler y ya.  Es como que las únicas 2 diferencias que veo.

    En fin, dije lo que dije porque no había notado que había 2 formas de agregar handlers.  Me pregunto si la que no genera ASHX funciona bien o no...

    Por cierto, gracias por lo de Response.End().  Lo he puesto por costumbre sin darle mucho pensamiento.


    Jose R. MCP
    Code Samples

    lunes, 2 de mayo de 2016 15:38
    Moderador
  • Sí, mira, en esta pantalla está un proyecto nuevo de ASP.NET WebForms en Visual Studio 2015, y tiene el Generic Handler (lo único que pasa es que hay que ir a la pestaña "general" para que aparezca). En el 2013 también tendría que aparecer. Cerciórate de que has cambiado a la pestaña correcta en la ventana de Añadir Nuevo Item.

    Por lo demás, el Generic Handler funciona igual que el ASP.NET Handler, pero te ahorras tener que registrarlo en el web.config; basta con añadir el .ashx al proyecto. Por lo demás el código interno es esencialmente el mismo.

    Add New Item

    lunes, 2 de mayo de 2016 17:23
  • Ok ok.  Comprendido.  Sí, es que no me di a entender correctamente, tal vez porque en un párrafo describía lo que hice sin saber, y en otro lo que descubrí después de buscar un poco.

    Sí, veo ambos, el ASP.NET Handler y Generic Handler en mi VS 2013.  Supongo que nunca puse mucha atención en cuanto al nombre que usaba hace años, y ahora cuando retomo las preguntas simplemente me voy por el primero que encuentro, el ASP.NET Handler.

    Entonces me queda claro:  El generic me ahorra el registro en web.config.  Como siempre, muy informativo. :-)


    Jose R. MCP
    Code Samples

    lunes, 2 de mayo de 2016 17:35
    Moderador
  • Buenas tardes a todos gracias por sus respuestas.

    me queda una duda.

    que sucede cuando por ejemplo el usuario cargo el producto pero olvido cargar la imagen y un cliente mostro la galeria.

    mi pregunta es cpomo hago a nivel del handler para que cuando no me traiga la foto o la foto es NULL me la cambien por otra foto q estara en una carpeta del servidor "fotogenerica.png" ?
    miércoles, 11 de mayo de 2016 19:40
  • Asumiendo que la función que lee los datos de SQL Server no se modifica, entonces lo que hay que modificar es ProcessRequest():

            public void ProcessRequest(HttpContext context)
            {
                //Obtengo la clave primaria que identifica
                int imgId = Int32.Parse(context.Request.QueryString["id"]);
                byte[] datos = null;
                try
                {
                    datos = SelectFotoPrueba(imgId);
                    context.Response.ContentType = "image/jpg";
                }
                catch (ArgumentException)
                {
                    //El ID no fue encontrado.  Devolver imagen genérica.
                    datos = LeerArchivo(context.Server.MapPath("~/folder/fotogenerica.png"));
                    context.Response.ContentType = "image/png";
                }
                catch (Exception)
                {
                    context.Response.StatusCode = (int)System.Net.HttpStatusCode.BadRequest;
                }
                context.Response.Clear();
                context.Response.BinaryWrite(datos);
            }

    Le queda de tarea hacer la función LeerArchivo(), que abre un archivo para lectura y devuelve su contenido en forma de un arreglo de bytes.


    Jose R. MCP
    Code Samples

    miércoles, 11 de mayo de 2016 20:28
    Moderador
  • Hola..

    Yo estoy tratando de mostrar una imagen que tengo guardada en la base de datos, pero no la muestra.

    estoy usando el generi HttpHandler C# Visual estudio 2015

    No se que pasa. sale la imagen que se muestra cuando no se consigue la imagen. Agradezco me colaboren pronto. Gracias.


    lunes, 16 de mayo de 2016 7:44
  • Hola.

    Ayuda urgente.

    Por favor estoy tratando de manejar el Handler, lo agrego al proyecto  pero cuando lo accedo desde el GridView y no me muestra la imagen. 

    No me deja insertar imágenes para que verifiquen como sale. 

    lunes, 16 de mayo de 2016 16:36