Monthly Archives: July 2007

View State

0
Filed under .NET Development

La mayoría de las veces tenemos que almacenar información entre peticiones de página. Como siempre, tenemos muchas maneras de hacer esto. La regla general es que si lo que precisamos es seguridad, administramos el estado del lado del servidor. Si lo que precisamos es rendimiento utilizamos administración de estado del lado del cliente.
Al utilizar administración de estado del lado del cliente es recomendable no almacenar información sensible. El método más común para almacenar información del lado del cliente es utilizar View State. ASP.NET utiliza View State para registrar los valores en los controles.
Generalmente las personas (incluso yo lo creía) creen que el View State está encriptado. Esto no es así. El View State es información serializada que después para que pueda ser almacenada en el HTML se convierte a una cadena Base-64.

Demostración:

        protected void Page_Load(object sender, EventArgs e)
        {
            ViewState.Add("NicolasFerreira", "http://www.nicolasferreira.com/");
        }

Salida HTML:

    <input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUJNzgzNDMwNTMzDxYCHg9OaWNvbGFzRmVycmVpcmEFH2h0dHA6Ly93d3cubmljb2xhc2ZlcnJlaXJhLmNvbS9kZJUtdPnrrPUmYC4xJVygWF1HWkH4" />

Si ponemos el siguiente código:

        protected void Page_Load(object sender, EventArgs e)
        {
            LosFormatter lLosFormatter = new LosFormatter();
            Pair lPair = (Pair)lLosFormatter.Deserialize
                ("/wEPDwUJNzgzNDMwNTMzDxYCHg9OaWNvbGFzRmVycmVpcmEFH2h0dHA6Ly93d3cubmljb2xhc2ZlcnJlaXJhLmNvbS9kZJUtdPnrrPUmYC4xJVygWF1HWkH4");
            ArrayList lArrayList = (ArrayList)((Pair)((Pair)lPair.First).Second).First;
            System.Web.UI.IndexedString lIndexedString = (System.Web.UI.IndexedString)lArrayList[0];
            string lstring = (string)lArrayList[1];
        }

Y analizamos el valor de la propiedad “lIndexedString.Value” y el valor de la variable “lstring” vamos a ver lo siguiente:

lIndexedString.Value = “NicolasFerreira”
lstring = “http://www.nicolasferreira.com/”

Lo que demuestra esto es que por defecto el View State no viene encriptado.
Aunque el código no se encuentre claro esto es simplemente para demostrar que el View State no viene encriptado por defecto.
Vamos a lo que nos interesa… encriptar el View State estableciendo el ajuste en el archivo Web.config (también lo podemos hacer en la directiva @Page para cada página):

<configuration>
  <system.web>
    <pages viewStateEncryptionMode="Always"/>
  </system.web>
</configuration>

Si repetimos el primer ejemplo:

        protected void Page_Load(object sender, EventArgs e)
        {
            ViewState.Add("NicolasFerreira", "http://www.nicolasferreira.com/");
        }

Vamos a obtener algo como esto:

<input type="hidden" name="__VIEWSTATE" id="Hidden1" value="NjodhOfUMQ+Kgz07a092dExYHtdZWVkyuNupSqW0xvO/4lwpBtc3LbYKkBKD21GbNaxzWvLvuGfO9zSzRg7HjmL3JZlHeQVjvvHNAxKAfp0=" />

Es obvio que “value” no se ve como una cadena codificada en Base-64, por consiguiente, View State está encriptado.
Si intentamos repetir el segundo ejemplo pasando esta cadena al método Deserialize de LosFormatter vamos a obtener una excepción:

System.ArgumentException
{“Los datos serializados no son válidos.”}

FileUpload

0
Filed under .NET Development

A continuación voy a demostrar cómo crear un sitio Web sencillo para subir archivos al servidor.

1º: Abrimos Visual Studio 2005.
2º: Menú File -> New -> Web Site…
3º: Seleccionamos ubicación (en este caso voy a elegir File System), y después Language (para este ejemplo voy a utilizar Visual Basic).
4º: Clic en OK.

Vamos a ver que Visual Studio crea automáticamente una página llamada Default.aspx y nos deja en el código de marcas.

5º: Hacemos clic derecho sobre Default.aspx en el explorador de solución y elegimos View Desginer.
6º: Desde el Toolbox, arrastramos hacia la página un control FileUpload y otro control Button.
7º: Hacemos doble clic en el control Button para que Visual Studio cree el manejador de evento por defecto (para el control Button es Click):

    Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click

    End Sub

Lo primero que tenemos que hacer es verificar que el usuario haya puesto algún archivo en el control FileUpload (que en HTML se renderiza como input type=”submit”).

    Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click
        If (Me.FileUpload1.HasFile) Then
            'Ha puesto un archivo.
        End If
    End Sub

Opcionalmente, podemos hacer validaciones como por ejemplo saber el tipo MIME del archivo, el tamaño en bytes del archivo subido, el nombre del archivo.

    Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click
        If (Me.FileUpload1.HasFile And Me.FileUpload1.PostedFile.ContentLength > 0 And Me.FileUpload1.PostedFile.ContentType = "image/bmp") Then
        End If
    End Sub

Después de esto podemos tomar diferentes caminos dependiendo de lo que se desee hacer. Para guardar el archivo a una carpeta podemos hacer lo siguiente:

    Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click
        If (Me.FileUpload1.HasFile And Me.FileUpload1.PostedFile.ContentLength > 0 And Me.FileUpload1.PostedFile.ContentType = "image/bmp") Then
            Me.FileUpload1.SaveAs(Server.MapPath(System.IO.Path.Combine("~/Uploads/", System.IO.Path.GetFileName(FileUpload1.PostedFile.FileName))))
        End If
    End Sub

Con Server.MapPath lo que hacemos es obtener la ruta física en el servidor Web que corresponde a la ruta virtual especificada.
Con System.IO.Path.Combine combinamos dos rutas (prefiero hacer esto antes que concatenar las cadenas a mano).
Con System.IO.Path.GetFileName obtenemos el nombre de archivo con la extensión de la ruta especificada. Si vemos el código, le paso FileUpload1.PostedFile.FileName. FileUpload1.PostedFile.FileName es el nombre del archivo que el usuario tiene en su computadora.
Por supuesto que a esto le faltan bastantes validaciones, como por ejemplo en el caso de que el directorio Uploads no exista la aplicación se va a caer. Podemos arreglarlo haciendo esto:

    Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click
        If (Me.FileUpload1.HasFile And Me.FileUpload1.PostedFile.ContentLength > 0 And Me.FileUpload1.PostedFile.ContentType = "image/bmp") Then
            If Not (System.IO.Directory.Exists(Server.MapPath("~/Uploads/"))) Then
                System.IO.Directory.CreateDirectory(Server.MapPath("~/Uploads/"))
            End If
            Me.FileUpload1.SaveAs(Server.MapPath(System.IO.Path.Combine("~/Uploads/", System.IO.Path.GetFileName(FileUpload1.PostedFile.FileName))))
        End If
    End Sub

También lo que va a pasar si alguien intenta subir un archivo con un nombre de archivo que ya existe en la carpeta Uploads es que el archivo viejo se va a sobrescribir.
Una cosa que hay que tener en cuenta es el límite de tamaño que el cliente tiene al subir un archivo. Para averiguar la configuración del sitio para saber a cuánto estamos limitados podemos utilizar lo siguiente:

    Private Function MaxRequestLength() As Integer
        Dim lMaxRequestLengthRetorno As Integer
        Dim lObjHttpRuntimeSection As Object = System.Web.Configuration.WebConfigurationManager.GetWebApplicationSection("system.web/httpRuntime")
        If (Not lObjHttpRuntimeSection Is Nothing And lObjHttpRuntimeSection.GetType Is GetType(System.Web.Configuration.HttpRuntimeSection)) Then
            Dim lHttpRuntimeSection As System.Web.Configuration.HttpRuntimeSection = DirectCast(lObjHttpRuntimeSection, System.Web.Configuration.HttpRuntimeSection)
            lMaxRequestLengthRetorno = lHttpRuntimeSection.MaxRequestLength
        End If
        Return lMaxRequestLengthRetorno
    End Function

MSDN dice lo siguiente sobre MaxRequestLength:

Valor de propiedad
Tamaño máximo de la solicitud en kilobytes. El tamaño predeterminado son 4096 KB (4 MB).

Comentarios
Se puede utilizar la propiedad MaxRequestLength para evitar ataques de denegación de servicio producidos por usuarios que envían archivos de gran tamaño al servidor.

Para cambiar el tamaño, agregamos un archivo de configuración Web y dentro de la sección system.web especificamos lo siguiente:

  <system.web>
    <httpRuntime maxRequestLength="4096"/>
  </system.web>

Cross-page Posting

0
Filed under .NET Development

A menudo tenemos que pasar información entre formularios. Esto lo podemos hacer de diferentes maneras. La buena (para mi) implica utilizar algo denominado “Cross-page Posting” (también depende de la situación, pero en general funciona). La mala (también para mí) implica utilizar técnicas como por ejemplo guardar la información en sesión. Cross-page Posting se da cuando un control está configurado para realizar PostBack a una página Web diferente. Para aclarar todo esto vamos a empezar con un ejemplo en donde tenemos la página “padre” y la página “hija”.
En el code-behind de la página padre exponemos propiedades públicas para que puedan ser accedidas por la página hija.

        public string Contraseña
        {
            get
            {
                return this.TxtContraseña.Text;
            }
        }
        public string NombreUsuario
        {
            get
            {
                return this.TxtNombreUsuario.Text;
            }
        }

Establecemos la propiedad PostBackUrl del botón que va a ser el encargado de hacer el PostBack a la página hija con la ruta virtual de la página hija, quedando el código de marcas de la siguiente manera:

        <asp:Label ID="LblNombreUsuario" runat="server" Text="Nombre De Usuario:"></asp:Label>
        <asp:TextBox ID="TxtNombreUsuario" runat="server"></asp:TextBox>
        <asp:Label ID="LblContraseña" runat="server" Text="Contraseña:"></asp:Label>
        <asp:TextBox ID="TxtContraseña" runat="server"></asp:TextBox>
        <asp:Button ID="BtnAceptar" runat="server" PostBackUrl="~/Child.aspx" Text="Aceptar" />

Ahora en el código de marcas de la página hija debajo de la directiva de página ponemos lo siguiente:

<%@ PreviousPageType VirtualPath="~/Parent.aspx" %>

Compilamos la aplicación para que IntelliSense agarre desde el code-behind de la página hija lo siguiente:

    protected void Page_Load(object sender, EventArgs e)
    {
        if (PreviousPage != null)
        {
            this.LblContraseñaValor.Text =
                Server.HtmlEncode(this.PreviousPage.Contraseña);
            this.LblNombreUsuarioValor.Text =
                Server.HtmlEncode(this.PreviousPage.NombreUsuario);
        }
    }

Al haber agregado en el código de marcas de la página hija la directiva PreviousPageType y establecido VirtualPath a “~/Parent.aspx”, desde el code-behind de la página hija la propiedad PreviousPage toma el tipo de la página padre. El resultado es que tenemos un Cross-page Posting de tipo seguro.