Serialización[Ejemplo de serialización de una clase]Fecha: 18/Mar/2005 (08-03-05)
|
Este artículo trata de explicar mediante un ejemplo como serializar(binaria) de una forma sencilla.
¿Qué es la Serialización?
La serialización es el proceso de convertir el estado de un objeto a un formato que se pueda almacenar o transportar. El complemento de la serialización es la deserialización, que convierte una secuencia en un objeto. Juntos, estos procesos permiten almacenar y transferir fácilmente datos.
El .Net FrameWork proporciona un amplío mecanismo para realizar la serialización y varias características que permiten personalizar el proceso.
Para el ejemplo vamos a centrarnos en la Serialización Binaria, y dentro de está, en la Básica.
Antes de Serializar tenemos que tener muy en cuenta las directrices de serialización:
EL EJEMPLO:
Queremos realizar una aplicación a modo de álbum a través de la cual podamos compartir imágenes. Cualquier persona deberá poder visualizar e insertar imágenes. Además, el álbum debe ser portable, así que en cualquier momento se podrá guardar una copia en el equipo del usuario mediante un fichero, a través del cual el usuario podrá ver y recuperar las fotos que en su día guardó. Las imágenes no se van a poder guardar ni en la BBDD ni subirlas al servidor como simples ficheros. Como ejemplo del ejemplo ;)))) se me ocurre un caso personal, la semana pasada fui a comer al pueblo de de mi hermana con toda la familia y nos hicimos muchas fotos con su cámara y la de mi primo, pero mi hermana es una de esas personas que tiene una cámara digital de muchos megapixel y no tiene ordenador, así que no podíamos grabar las fotos en Cd's, además como mi familia esta desperdigada por mil y una ciudades tampoco nos servía llevarnos la cámara e imprimirlas en una tienda. Por lo que el único nexo en común era Internet, todos teníamos acceso de algún modo(casa, trabajo...) así que me decidí por hacer una página donde mi hermana y mi primo pudieran subir las imágenes de forma que el resto pudiéramos tener acceso a ellas y guardarlas todas para luego recuperarlas, de forma que solo nos quedaríamos con las que nos gustasen más.
Si nos paramos a analizar la mejor forma de implementar esta solución es posible que se difiera bastante de la forma, pero no es el objetivo del artículo enseñar a realizar esta solución de la forma más optima.
Empecemos...Como necesitamos guardar las fotos para que el usuario las pueda recuperar, sin BBDD, utilizaremos un DataTable para almacenar las fotos, éste será miembro de una clase, la cual será lo que serializaremos para así poder crear un copia exacta de lo que contiene el DataTable y poder recuperarlo cuando queramos. Al serializar la clase tendremos todos sus miembros, propiedades, etc. accesibles al DesSerializar. Para marcar una clase como serializable:
<Serializable()> Public Class SerializarDataTable End ClassTambíen podemos marcar elementos de la clase como no serializables,
<NonSerialized()> Private _DataTable as DataTable
Vamos a crear la clase(es muy básica, ni elimina ni ordena elementos) que contendrá el Datable con las fotos:
<Serializable()> Public Class SerializarDataTable Private _Dt As New DataTable Private Function Insertar(ByVal strNombre As String, ByVal Imagen As Byte()) Dim Dr As DataRow If _Dt.Rows.Count = 0 Then _Dt.Columns.Add(New DataColumn("NumeroFoto", GetType(Integer))) _Dt.Columns.Add(New DataColumn("NombreFoto", GetType(String))) _Dt.Columns.Add(New DataColumn("Foto", GetType(Byte()))) End If Dr = _Dt.NewRow Dr("NumeroFoto") = _Dt.Rows.Count Dr("NombreFoto") = strNombre Dr("Foto") = Imagen _Dt.Rows.Add(Dr) End Function WriteOnly Property InsertarElemento(ByVal strNombre As String) As Byte() Set(ByVal Value As Byte()) Insertar(strNombre, Value) End Set End Property ReadOnly Property DataTable() As DataTable Get Return _Dt End Get End Property End ClassUna vez que tenemos la clase, veamos como se puede serializar.
Private Sub Serializar() Dim formatter As IFormatter = New BinaryFormatter 'formateador binario para realizar la serialización. ' Todo lo que necesita es crear una instancia de la secuencia y el formateador que desee utilizar, Dim stream As Stream = New FileStream(Server.MapPath("MiAlbum.alb"), FileMode.Create, FileAccess.Write, FileShare.Read) 'y a continuación, llamar al método Serialize en el formateador formatter.Serialize(stream, MiClase) stream.Close() End Sub Importantísimo para poder crear el archivo es tener permisos el usuario ASPNET en la carpeta. Para desSerializar:Private Sub DesSerializar() Dim formatter As IFormatter = New BinaryFormatter Dim stream As Stream = New FileStream(Server.MapPath("MiAlbum.alb"), FileMode.Open, FileAccess.Read, FileShare.Read) MiClase = formatter.Deserialize(stream) stream.Close() End SubInformación sobre Stream:
Para mostrar los registros del DataTable utilizaremos un Datalist*
cada registro mostrará el nombre de la Foto y la propia Foto, aquí nos encontramos "un problema" ya que la foto no se encuentra en un fichero físico en el servidor, esta se encuentra como una Matriz de Bytes, por lo que si queremos verla antes tenemos que transformarla, esto lo realizamos con el método Response.BinaryWrite, lo más recomendable sería indicar el ContentType con el método, response.ContentType*.
'Tenemos un DataTable que contiene todas las fotos, para saber que foto mostrar 'pasamos el número de foto por parámetro If Not Request("NumeroFoto") = "" Then Dim FilasEncontras() As DataRow = MiDataTable.Select("NumeroFoto=" & Request("NumeroFoto")) If FilasEncontras.Length > 0 Then Response.BinaryWrite(FilasEncontras(0).Item("Foto")) End If End Ifde modo que necesitamos crear una página para visualizar imágenes y donde queramos mostrarla tendremos que llamar a la página con el número de la foto a mostrar:
Me.ImgFoto.Src = "verimagen.aspx?NumeroFoto=1"
como utilizaremos un DataList :
<img width="120" height="120" border="0" src='<%# DataBinder.Eval(Container.DataItem, "NumeroFoto", "verimagen.aspx?numeroFoto={0}") %>'></a>
Esta misma página tendrá funcionalidad par subir las imágenes, añadirlas al datable, mostrarlas y guardar el álbum resultante. Antes de entrar en detalle en la página nos centraremos en uno de los requisitos, "todos los usuarios pueden ver las imágenes que estamos subiendo", como nuestras imágenes no se encuentran en la BBDD o en el Servidor, declaramos la clase que contiene el Datable como compartida(Shared)
http://msdn.microsoft.com/library/spa/default.asp?url=/library/SPA/vblr7/html/vakeyShared.asp
Para subir las imágenes disponemos de un Input File, esta imagen nos puede dar una idea de como será nuestra página:
La foto que se encuentra en la parte superior de la página, es una previsualización de la foto que elegimos desde el Input File para así verla antes de insertarla. Cuando eligimos un fichero desde el Input File se desencadena el evento(cliente), onpropertychange, así que tenemos que realizar en ese evento una llamada al servidor que se encargue de mostrar la imagen, esta llamada puede ser la misma que se genera en un LinkButton, así que podemos colocar un LinkButton en la página, obtener la llamada cliente que genera y copiarla en el evento onpropertychange:
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load 'Introducir aquí el código de usuario para inicializar la página If Not IsPostBack Then Me.File1.Attributes.Add("onpropertychange", Page.GetPostBackClientHyperlink(lnkVerfoto, "")) End If End Sub Private Sub lnkVerfoto_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkVerfoto.Click If Me.File1.PostedFile.ContentLength > 0 Then .... End If End SubA lnkVerFoto no le ponemos texto para que no sea visible para el usuario. Veamos el código Html de la página:
<%@ Page Language="vb" AutoEventWireup="false" Codebehind="GuardarImagen.aspx.vb" Inherits="AlbumPersonalizado.GuardarImagen" %> <!DOCTYPE HTML PUBLIC "-"//W3C//DTD HTML 4.0 Transitional//EN"> <HTML> <HEAD> <title>Nuestras Fotos</title> <meta content="Microsoft Visual Studio .NET 7.1" name="GENERATOR"> <meta content="Visual Basic .NET 7.1" name="CODE_LANGUAGE"> <meta content="JavaScript" name="vs_defaultClientScript"> <meta content="http:"//schemas.microsoft.com/intellisense/ie5" name="vs_targetSchema"> </HEAD> <body MS_POSITIONING="GridLayout"> <form id="Form1" method="post" runat="server"> <table> <tr> <td align="center" colSpan="3"><IMG id="ImgFoto" height="100" runat="server"></td> </tr> <tr> <td colSpan="3"><INPUT id="File1" type="file" name="File1" runat="server"> <asp:linkbutton id="lnkVerfoto" runat="server" Visible="true" ></asp:linkbutton></td> </tr> <tr> <td noWrap width="10%" colSpan="1"><asp:label id="Label1" runat="server">Nombre de la imagen</asp:label> </td> <td tabIndex= <td tabIndex="0" width="10%" colSpan="1"><asp:textbox id="txtNombreImagen" runat="server"></asp:textbox></td> <td><asp:linkbutton id="lnkInsertar" runat="server">Insertar</asp:linkbutton></td> </tr> <tr> <td colSpan="3"><asp:linkbutton id="lnkActualizar" runat="server" CausesValidation="False"> Obtener nuevas imágenes</asp:linkbutton></td> </tr> <tr> <td colSpan="3"><asp:requiredfieldvalidator id="RfvErrores" runat="server" ErrorMessage="Inserta el nombre de la imagen" ControlToValidate="txtNombreImagen"></asp:requiredfieldvalidator></td> </tr> <tr> <td colSpan="3"><ASP:DATALIST id="DtlImagenes" runat="server" RepeatColumns="3"> <ItemTemplate> <TABLE style="FONT: 10pt verdana" cellPadding="10"> <TR> <TD width="1" bgColor="#bd8672"> <TD vAlign="top" width="120px"> <a id=hlkFoto target=_blank href='<%# DataBinder.Eval(Container.DataItem, "NumeroFoto", "verimagen.aspx?numeroFoto={0}") %>'> <img width="120" height="120" border="0" src='<%# DataBinder.Eval(Container.DataItem, "NumeroFoto", "verimagen.aspx?numeroFoto={0}") %>'></a></TD> <TD vAlign="top"><B>Título: </B> <%# DataBinder.Eval(Container.DataItem, "NombreFoto") %> <BR> <P></P> </TD> </TR> </TABLE> </ItemTemplate> </ASP:DATALIST></td> </tr> <tr> <td colSpan="3"><asp:linkbutton id="lnkSerializar" runat="server" CausesValidation="False">Guardar Álbum(Serializar)</asp:linkbutton> </td> </tr> </table> </form> </body> </HTML>El LinkButton "lnkActualizar" lo utilizamos para obtener las posibles imágenes que hayan insertado otros usuarios. Vamos con el código VB:
Imports System.Runtime.Serialization Imports System.IO Imports System.Runtime.Serialization.Formatters.Binary Public Class GuardarImagen Inherits System.Web.UI.Page #Region " Código generado por el Diseñador de Web Forms " 'El Diseñador de Web Forms requiere esta llamada. <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent() End Sub Protected WithEvents File1 As System.Web.UI.HtmlControls.HtmlInputFile Protected WithEvents ImgFoto As System.Web.UI.HtmlControls.HtmlImage Protected WithEvents lnkVerfoto As System.Web.UI.WebControls.LinkButton Protected WithEvents Label1 As System.Web.UI.WebControls.Label Protected WithEvents txtNombreImagen As System.Web.UI.WebControls.TextBox Protected WithEvents lnkInsertar As System.Web.UI.WebControls.LinkButton Protected WithEvents lnkActualizar As System.Web.UI.WebControls.LinkButton Protected WithEvents RfvErrores As System.Web.UI.WebControls.RequiredFieldValidator Protected WithEvents lnkSerializar As System.Web.UI.WebControls.LinkButton Protected WithEvents lnkDesSerializar As System.Web.UI.WebControls.LinkButton Protected WithEvents DtlImagenes As System.Web.UI.WebControls.DataList 'NOTA: el Diseñador de Web Forms necesita la siguiente declaración del marcador de posición. 'No se debe eliminar o mover. Private designerPlaceholderDeclaration As System.Object Private Sub Page_Init(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Init 'CODEGEN: el Diseñador de Web Forms requiere esta llamada de método 'No la modifique con el editor de código. InitializeComponent() End Sub #End Region 'Variable Compartida Public Shared MiClase As New SerializarDataTable Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load 'Introducir aquí el código de usuario para inicializar la página If Not IsPostBack Then Me.File1.Attributes.Add("onpropertychange", Page.GetPostBackClientHyperlink(lnkVerfoto, "")) End If End Sub Private Sub ObtenerDatos() If MiClase.DataTable.Rows.Count > 0 Then DtlImagenes.Visible = True DtlImagenes.DataSource = MiClase.DataTable DtlImagenes.DataBind() Else DtlImagenes.Visible = False End If End Sub Private Sub lnkVerfoto_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkVerfoto.Click If Me.File1.PostedFile.ContentLength > 0 Then 'Como es una previsualización, todavía no esta insertada en el datable, 'así que para poder mostrarla a través de nuestra página "verimagen.aspx" 'la guardamos en una Session y en "verimagen.aspx" nos encargamos de averiguar de que 'objeto obtener la imagen a mostrar. Session("Foto") = ObtenerBytes(Me.File1) End If Me.ImgFoto.Src = "verimagen.aspx" End Sub Private Function ObtenerBytes(ByVal ObjetoInput As HtmlInputFile) As Byte() 'Este función obtiene los Bytes que contiene el fichero del Input File y retorna un Matriz de Bytes Dim BytesDeLaImagen(ObjetoInput.PostedFile.ContentLength) As Byte ObjetoInput.PostedFile.InputStream.Position = 0 ObjetoInput.PostedFile.InputStream.Read(BytesDeLaImagen, 0, ObjetoInput.PostedFile.ContentLength - 1) Return BytesDeLaImagen End Function Private Sub lnkInsertar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkInsertar.Click If Not IsNothing(Session("Foto")) Then 'Insertamos la nueva imagen MiClase.InsertarElemento(Me.txtNombreImagen.Text) = Session("Foto") Me.txtNombreImagen.Text = "" Session("Foto") = Nothing ObtenerDatos() Else RfvErrores.ErrorMessage = "Por favor elije la imagen" RfvErrores.IsValid = False End If End Sub Private Sub Serializar() Dim formatter As IFormatter = New BinaryFormatter 'formateador binario para realizar la serialización. ' Todo lo que necesita es crear una instancia de la secuencia y el formateador que desee utilizar, 'y a continuación, llamar al método Serialize en el formateador 'Seralizamos el archivo en Memoria, el archivo se guarda en cliente por lo que no es necesario 'guardarlo en el servidor para a continuación forzar la descarga del archivo. Dim Flujo As Stream = New MemoryStream formatter.Serialize(Flujo, MiClase) 'Indicamos el tamaño de la Matriz Dim DatosFichero(Flujo.Length) As Byte Flujo.Position = 0 'leemos la secuencia de Bytes Dim BytesLeidos As Long = Flujo.Read(DatosFichero, 0, CInt(Flujo.Length)) ' le indicamos un ContentType para que no muestre como tipo predeterminado Html Response.ContentType = "application" 'Forzamos la descarga del archivo, para evitar que se habra dentro del navegador Response.AddHeader("Content-Disposition", "attachment; filename=MiAlbum.alb") 'Cabecera que establece el tamaño de la respuesta (tamaño del archivo en bytes) Response.AddHeader("Content-length", BytesLeidos.ToString()) Response.BinaryWrite(DatosFichero) Response.End() End Sub Private Sub lnkSerializar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkSerializar.Click Serializar() End Sub End ClassYa tenemos lo más importante, vamos con la página que muestra las imágenes.
<%@ Page Language="vb" AutoEventWireup="false" Codebehind="verImagen.aspx.vb" Inherits="AlbumPersonalizado.verImagen" %> <!DOCTYPE HTML PUBLIC "-"//W3C//DTD HTML 4.0 Transitional//EN"> <HTML> <HEAD> <title>verImagen</title> <meta content="Microsoft Visual Studio .NET 7.1" name="GENERATOR"> <meta content="Visual Basic .NET 7.1" name="CODE_LANGUAGE"> <meta content="JavaScript" name="vs_defaultClientScript"> <meta content="http:"//schemas.microsoft.com/intellisense/ie5" name="vs_targetSchema"> </HEAD> <body MS_POSITIONING="GridLayout"> <form id="Form1" method="post" runat="server"> </form> </body> </HTML>Observar que la página no contiene ningún control, pues servirá como contenedora de las imágenes, lo más importante es el código VB:
Imports System.Runtime.Serialization Imports System.IO Imports System.Runtime.Serialization.Formatters.Binary Public Class verImagen Inherits System.Web.UI.Page #Region " Código generado por el Diseñador de Web Forms " 'El Diseñador de Web Forms requiere esta llamada. <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent() End Sub 'NOTA: el Diseñador de Web Forms necesita la siguiente declaración del marcador de posición. 'No se debe eliminar o mover. Private designerPlaceholderDeclaration As System.Object Private Sub Page_Init(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Init 'CODEGEN: el Diseñador de Web Forms requiere esta llamada de método 'No la modifique con el editor de código. InitializeComponent() End Sub #End Region 'Accedemos a la página que contiene la variable compartida Dim Pagina As GuardarImagen Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load 'Introducir aquí el código de usuario para inicializar la página 'Tenemos un DataTable que contiene todas las fotos, para saber que foto mostrar 'pasamos el número de foto por parámetro If Not Request("NumeroFoto") = "" Then Dim FilasEncontras() As DataRow = Pagina.MiClase.DataTable.Select("NumeroFoto=" & Request("NumeroFoto")) If FilasEncontras.Length > 0 Then Response.BinaryWrite(FilasEncontras(0).Item("Foto")) End If ElseIf Not IsNothing(Session("Foto")) Then 'cuando estamos en este caso es para la previsualización de las imágenes. Response.BinaryWrite(Session("Foto")) Else 'si estamos en este caso es que estamos visualizando el álbum guardado Dim FilasEncontras() As DataRow = Session("MiAlbum").Select("NumeroFoto=" & Request("numeroFotoMiAlbum")) If FilasEncontras.Length > 0 Then Response.BinaryWrite(FilasEncontras(0).Item("Foto")) End If End If End Sub End ClassY por último queda la página para ver los álbumes guardados, está página es prácticamente una copia de GuardarImagen.aspx
<%@ Page Language="vb" AutoEventWireup="false" Codebehind="VerAlbum.aspx.vb" Inherits="AlbumPersonalizado.VerAlbum" %> <!DOCTYPE HTML PUBLIC "-"//W3C//DTD HTML 4.0 Transitional//EN"> <HTML> <HEAD> <title>WebForm1</title> <meta content="Microsoft Visual Studio .NET 7.1" name="GENERATOR"> <meta content="Visual Basic .NET 7.1" name="CODE_LANGUAGE"> <meta content="JavaScript" name="vs_defaultClientScript"> <meta content="http:"//schemas.microsoft.com/intellisense/ie5" name="vs_targetSchema"> </HEAD> <body MS_POSITIONING="GridLayout"> <form id="Form1" method="post" encType="multipart/form-data" runat="server"> <table> <tr> <td align="center" colSpan="3"></td> </tr> <tr> <td colSpan="3"><INPUT id="File1" type="file" name="File1" runat="server"> <asp:linkbutton id="lnkDesSerializar" runat="server" CausesValidation="False">Ver Albúm(DesSerializar)</asp:linkbutton></td> </tr> <tr> <td style="HEIGHT: 20px" noWrap width="10%" colSpan="1"> </td> <td style="HEIGHT: 20px" tabIndex="0" width="10%" colSpan="1"></td> <td style="HEIGHT: 20px"></td> </tr> <tr> <td colSpan="3"> <asp:RequiredFieldValidator id="RfVErrores" runat="server" ErrorMessage="Elija un archivo " ControlToValidate="File1"></asp:RequiredFieldValidator></td> </tr> <tr> <td colSpan="3"></td> </tr> <tr> <td colSpan="3"><ASP:DATALIST id="MyDataList" runat="server" RepeatColumns="3"> <ItemTemplate> <TABLE style="FONT: 10pt verdana" cellPadding="10"> <TR> <TD width="1" bgColor="#bd8672"> <TD vAlign="top" width="120px"> <a id=hlkFoto target=_blank href='<%# DataBinder.Eval(Container.DataItem, "NumeroFoto", "verimagen.aspx?numeroFotoMiAlbum={0}") %>'> <img width="120" height="120" border="0" src='<%# DataBinder.Eval(Container.DataItem, "NumeroFoto", "verimagen.aspx?numeroFotoMiAlbum={0}") %>'></a></TD> <TD vAlign="top"><B>Título: </B> <%# DataBinder.Eval(Container.DataItem, "NombreFoto") %> <BR> <P></P> </TD> </TR> </TABLE> </ItemTemplate> </ASP:DATALIST></td> </tr> <tr> <td colSpan="3"> </td> </tr> </table> </form> </body> </HTML>Y el VB:
Imports System.Runtime.Serialization Imports System.IO Imports System.Runtime.Serialization.Formatters.Binary Public Class VerAlbum Inherits System.Web.UI.Page #Region " Código generado por el Diseñador de Web Forms " 'El Diseñador de Web Forms requiere esta llamada. <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent() End Sub Protected WithEvents MyDataList As System.Web.UI.WebControls.DataList Protected WithEvents lnkDesSerializar As System.Web.UI.WebControls.LinkButton Protected WithEvents File1 As System.Web.UI.HtmlControls.HtmlInputFile Protected WithEvents RfVErrores As System.Web.UI.WebControls.RequiredFieldValidator 'NOTA: el Diseñador de Web Forms necesita la siguiente declaración del marcador de posición. 'No se debe eliminar o mover. Private designerPlaceholderDeclaration As System.Object Private Sub Page_Init(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Init 'CODEGEN: el Diseñador de Web Forms requiere esta llamada de método 'No la modifique con el editor de código. InitializeComponent() End Sub #End Region Dim MiClaseAlbum As SerializarDataTable Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load 'Introducir aquí el código de usuario para inicializar la página End Sub Private Sub lnkDesSerializar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkDesSerializar.Click Dim formatter As IFormatter = New BinaryFormatter File1.PostedFile.InputStream.Position = 0 'obtenemos el fichero del Input File y la DesSerializamos. MiClaseAlbum = formatter.Deserialize(File1.PostedFile.InputStream) Me.ObtenerDatos() End Sub Private Sub ObtenerDatos() If MiClaseAlbum.DataTable.Rows.Count > 0 Then MyDataList.Visible = True Session("MiAlbum") = MiClaseAlbum.DataTable MyDataList.DataSource = MiClaseAlbum.DataTable MyDataList.DataBind() Else MyDataList.Visible = False End If End Sub End Class
Espacios de nombres usados en el código de este artículo:
System.Runtime.Serialization
System.Io
System.Runtime.Serialization.Formatters.Binary